너무나 작고, 강력하고, 싸네요.


괜히 아트멜 Wifi 모듈을 쓸 이유가 없습니다.



https://www.seeedstudio.com/ESP3212-Wifi-Bluetooth-Combo-Module-p-2706.html



https://www.seeedstudio.com/WiFi-Serial-Transceiver-Module-Breakout-Board-w%26-ESP8266-p-2081.html


안녕하세요.


회사를 옮기면서 쓰게될 프로세서를 소개합니다.

이 프로세서(Psoc-5LP)는 ARM Cortex-M3계열로서,
특이점은 OP-AMP 와 ADC, ASIC을 Microcontroller와 조합해서 사용한다는 점입니다. 
This type of system is different from most mixed-signalembedded systems, which use a combination of amicrocontroller unit (MCU) and external analog and digitalperipherals, such as opamps, ADCs, and applicationspecificintegrated circuits (ASICs). 

다른 일반적인 MCU는 정해진 핀의 기능중 선택해서 사용하는 방식이나, 이 MCU(psoc 5LP)는 ASIC 회로를 꾸미고 핀에 붙이고 조합해서 씁니다. 예를 들어 디지탈 필터니 카운터, 디지탈 플립플롭들을 ASIC하듯이 회로도처럼 설계하여 MCU핀에 붙이면
외부에 Logic IC를 쓴 것과 같은 효과가 있습니다.
이 점이 저한테는 굉장히 특이하더군요.

컴파일러는 Cypress에서 제공하는 Psoc Creator 3.1을 사용합니다.
현재 공부를 진행 중인데, 왠만한 기능들이 라이브러리화되어 제공되고 라이브러리에서
회로도 툴(Orcad 와 비슷)처럼 부품을 빼서 그리면 기본 소스 코드가 생성됩니다.

공부를 진행하면서 시리즈로 올려볼까 합니다.

현재는 아직 프로그램 다운로드나 컴파일도 서툽니다. ^^





일반적인 JTAG으로 다운로드하는 프로그램과 다 똑같고,

수정할 부분은 다음의 3가지 입니다.

1. configuration 의 target TAB의 IROM 영역 설정.



2. DFUse 프로그램에서 DFU 전용 업그레이드 프로그램으로 변환하기 위해서 HEX 파일 출력 설정.



3. system_stm32f4xx.c 파일에서 VECTER Table Offset 값 수정.



이렇게 설정하고 컴파일을 하면 HEX 파일이 나옵니다.
이 파일을 DFU 파일로 변환합니다.

다음은 변환툴(HEX ->DFU) 입니다. STM에서 DFUSe 툴을 검색해서 인스톨하면 같이 깔리는 DFU File Manager 라는 툴이 있습니다.
1. 이 프로그램을 실행해서 HEX->DFU 옵션을 선택하고 OK.



2. Vendor ID를 0x0483 ,Product ID 를 0xDF11 로 설정하고 Version 은 적당하게 설정합니다.
3. S19 or HEX 버튼을 클릭해서 위에서 만들어진 APP 프로그램의 결과물인 HEX 파일을 선택한다.



4. Generate 버튼을 눌러서 DFU 파일을 생성한다.



지금까지 DFU Upgrade용 파일을 생성하는 과정이었고, 다음은 이 DFU 파일을 Upload 하는 과정입니다.
1. DFUSe Demo 프로그램을 실행한다.



2. DFU Bootloader 가 실행될 조건으로 보드를 설정하여 컴퓨터의 USB 포트에 연결하면 다음과 같은 내부 정보가 DFUSe 프로그램에 표시된다.



3. Upgrade or Verify Action 구간에 있는 Choose 버튼을 클릭해서 DFU Manager 에서 만든 DFU 파일을 읽어 온다.



4. Upgrade 버튼을 클릭하여 프로그램을 Board에 Upgrade 한다.



5. Upgrade 가 완료 되면 다음과 같이 Upgrade Successful! 메세지가 뜬다.



6. 리셋 버튼을 눌러주면 부트로더 프로그램에서 APP 가 정상인지를 체크해서 APP 프로그램으로 점프해서 APP 프로그램이 실행된다.




안녕하세요, 얼른 올려야 했는데 너무 늦었네요.


지난 번에 CUBEMX 툴로 부트로더 만들어서 실행하면 윈도우 장치 드라이버에 DFU 드라이버가 생성되는 것 까지 했었죠.

여기까지 1차로 진행이 잘 된 것이고,

두번째로 추가할 코드들이 있습니다.

두개의 파일에 손을 대야 합니다. 
main.c  과 usbd_dfu_if.c 입니다.

먼저 main.c 에서 고쳐야 할 내용은 다음과 같습니다.
1. APP 프로그램(부트로더 프로그램이 아닌) 이 현재 USBD_DFU_APP_DEFAULT_ADD 위치에 들어 있는지 검사하는 내용.
2. APP 가 존재하면 APP 프로그램으로 점프.
3. 강제로 부트로더 진입조건을 설정하여 DFU Upgrade 모드로 들어갈 수 있다.(User Switch를 누름으로써)
3. 존재하지 않으면 DFU Upgrade 프로그램 실행.
4. MX_USB_DEVICE_Init() 함수는 Cube MX 툴이 Source 코드를 생성할 때마다 자동으로 다시 위치를 앞쪽에 잡아 놓는데, APP 프로그램이 메모리에 없을때만 동작하도록 뒷쪽으로 위치 이동 시켜야 합니다.

해당 코드는 다음과 같습니다.
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
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_USART2_UART_Init();
  //MX_USB_DEVICE_Init();
 
  /* USER CODE BEGIN 2 */
    HAL_Delay(500);
    printf("DFU Program Start.. \n\r");
 
    // Test if User button on the Necleo kit is pressed 
  if (HAL_GPIO_ReadPin(USER_SW_IN_GPIO_Port,USER_SW_IN_Pin) != GPIO_PIN_RESET)
  {
            printf("USBD_DFU_APP_DEFAULT_ADD ..[%08X]\n\r",(*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD));
    // Check Vector Table: Test if user code is programmed starting from address 
    //   "APPLICATION_ADDRESS" 
    if (((*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD) & 0x2FFE0000 ) == 0x20000000)
    {
            printf("APP Start.. \n\r");
      // Jump to user application 
      JumpAddress = *(__IO uint32_t*) (USBD_DFU_APP_DEFAULT_ADD + 4);
      Jump_To_Application = (pFunction) JumpAddress;
      // Initialize user application's Stack Pointer 
      __set_MSP(*(__IO uint32_t*) USBD_DFU_APP_DEFAULT_ADD);
      Jump_To_Application();
    }
  }
    
    printf("DFU Upgrade Mode Start.. \n\r");
  MX_USB_DEVICE_Init();
 
  /* 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

여기서 설명이 좀 필요한 부분이 APP 시작 주소를 검사하여 프로그램이 메모리에 들어있는지를 검사하는 코드인데,
해당 코드는 다음과 같습니다.
1
2
3
4
    if (((*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD) & 0x2FFE0000 ) == 0x20000000)
    {
 
    }
cs

APP 시작 주소(USBD_DFU_APP_DEFAULT_ADD)는 나중에 부트로더에 의해 Upload될 APP프로그램이 위치할 시작 주소입니다.
이 위치에는 Stack 포인터가 위치하는데, 이 Stack Pointer 는 램 영역 내에 주소값을 갖어야 정상적인 프로그램으로 인식합니다.
128KByte 의 램영역은 0x2000 0000 ~ 0x2001 FFFF 입니다.

위의 계산식을 풀어서 쓰면 
1
2
3
4
if ( (*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD) >= 0x20000000  && \
     (*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD) <  0x20020000       )
{
}
cs
와 같습니다.
풀어서 보면 쉽게 아시겠죠? 저도 처음에 왜 이렇게 쓰는지 골머리를 알았습니다. ^^

그다음에 __set_MSP() 함수로 스택포인터를 초기화한 후, user Application 프로그램으로 점프하면 Upload된 user 프로그램이 실행됩니다.


이번에는 usbd_dfu_if.c 의 내용을 수정해야 하는데, 추가할 내용이 꽤 많네요.
내용의 설명은 하지 않고 수정할 코드만 표시하겠습니다. 내용은 내부 Flash 메모리 읽기/쓰기/지우기 내용들입니다.
내부 Flash 메모리 읽기/쓰기/지우기 내용은 나중에 따로 다룰 예정입니다.

다음과 같은 함수를 추가합니다.
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
/* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */
static uint32_t GetSectorSize(uint32_t Sector);
static uint32_t GetSector(uint32_t Address);
/* USER CODE END PRIVATE_FUNCTIONS_DECLARATION */
 
/* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */
static uint32_t GetSector(uint32_t Address)
{
  uint32_t sector = 0;
  
  if((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0))
  {
    sector = FLASH_SECTOR_0;  
  }
  else if((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1))
  {
    sector = FLASH_SECTOR_1;  
  }
  else if((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2))
  {
    sector = FLASH_SECTOR_2;  
  }
  else if((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3))
  {
    sector = FLASH_SECTOR_3;  
  }
  else if((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4))
  {
    sector = FLASH_SECTOR_4;  
  }
  else if((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5))
  {
    sector = FLASH_SECTOR_5;  
  }
  else if((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6))
  {
    sector = FLASH_SECTOR_6;  
  }
  else/*(Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_7))*/
  {
    sector = FLASH_SECTOR_7;  
  }
 
  return sector;
}
 
/**
  * @brief  Gets sector Size
  * @param  None
  * @retval The size of a given sector
  */
static uint32_t GetSectorSize(uint32_t Sector)
{
  uint32_t sectorsize = 0x00;
  if((Sector == FLASH_SECTOR_0) || (Sector == FLASH_SECTOR_1) || (Sector == FLASH_SECTOR_2) ||\
     (Sector == FLASH_SECTOR_3) )
  {
    sectorsize = 16 * 1024;
  }
  else if(Sector == FLASH_SECTOR_4)
  {
    sectorsize = 64 * 1024;
  }
  else
  {
    sectorsize = 128 * 1024;
  }  
  return sectorsize;
}
 
cs

내용이 빈 함수들에 다음과 같이 추가합니다.

uint16_t MEM_If_Init_FS(void) 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
uint16_t MEM_If_Init_FS(void)
  /* USER CODE BEGIN 0 */ 
  return (USBD_OK);
  /* USER CODE END 0 */ 
}
 
// 다음과 같이 수정
uint16_t MEM_If_Init_FS(void)
  /* USER CODE BEGIN 0 */ 
    HAL_FLASH_Unlock();  
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |  
                           FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);  
  return (USBD_OK);
  /* USER CODE END 0 */ 
}
cs

uint16_t MEM_If_DeInit_FS(void) 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
uint16_t MEM_If_DeInit_FS(void)
  /* USER CODE BEGIN 1 */ 
  return (USBD_OK);
  /* USER CODE END 1 */ 
}
 
// 다음과 같이 수정
uint16_t MEM_If_DeInit_FS(void)
  /* USER CODE BEGIN 1 */ 
    HAL_FLASH_Lock();  
  return (USBD_OK);
  /* USER CODE END 1 */ 
}
cs


uint16_t MEM_If_Erase_FS(uint32_t Add) 함수
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
uint16_t MEM_If_Erase_FS(uint32_t Add)
{
  /* USER CODE BEGIN 2 */ 
  return (USBD_OK);
  /* USER CODE END 2 */ 
}
 
// 다음과 같이 수정
uint16_t MEM_If_Erase_FS(uint32_t Add)
{
  /* USER CODE BEGIN 2 */ 
    uint32_t UserStartSector;  
    uint32_t SectorError;  
    FLASH_EraseInitTypeDef pEraseInit;  
    MEM_If_Init_FS();  
    
    UserStartSector = GetSector(Add);  
    
    pEraseInit.TypeErase = TYPEERASE_SECTORS;  
    pEraseInit.Sector = UserStartSector;  
    pEraseInit.NbSectors = 3;  
    pEraseInit.VoltageRange = VOLTAGE_RANGE_3;  
    if(HAL_FLASHEx_Erase(&pEraseInit,&SectorError)!=HAL_OK)  
    {  
            return (USBD_FAIL);  
    }     
  return (USBD_OK);
  /* USER CODE END 2 */ 
}
cs

uint16_t MEM_If_Write_FS(uint8_t *src, uint8_t *dest, uint32_t Len) 함수
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
uint16_t MEM_If_Write_FS(uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* USER CODE BEGIN 3 */ 
  return (USBD_OK);
  /* USER CODE END 3 */ 
}
 
// 다음과 같이 수정
uint16_t MEM_If_Write_FS(uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* USER CODE BEGIN 3 */ 
    uint32_t i = 0;   
    for(i = 0; i < Len; i = i + 4)  
    {  
            if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,(uint32_t)(dest + i),*(uint32_t *)(src + i)) == HAL_OK)  
            {  
                    if(*(uint32_t *)(src + i) != *(uint32_t *)(dest + i))  
                    {  
                            return 2;  
                    }  
            }  
            else  
            {  
                    return 1;  
            }  
    }     
  return (USBD_OK);
  /* USER CODE END 3 */ 
}
cs

uint8_t *MEM_If_Read_FS (uint8_t *src, uint8_t *dest, uint32_t Len)함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
uint8_t *MEM_If_Read_FS (uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* Return a valid address to avoid HardFault */
  /* USER CODE BEGIN 4 */ 
  return (uint8_t*)(USBD_OK);
  /* USER CODE END 4 */ 
}
 
// 다음과 같이 수정
uint8_t *MEM_If_Read_FS (uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* Return a valid address to avoid HardFault */
  /* USER CODE BEGIN 4 */ 
    uint32_t i = 0;  
    uint8_t *psrc = src;  
    for( i = 0; i < Len ; i++ )  
    {  
            dest[i] = *psrc++;  
    }  
  return (uint8_t*)(USBD_OK);
  /* USER CODE END 4 */ 
}
cs


uint16_t MEM_If_GetStatus_FS (uint32_t Add, uint8_t Cmd, uint8_t *buffer) 함수
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
uint16_t MEM_If_GetStatus_FS (uint32_t Add, uint8_t Cmd, uint8_t *buffer)
{
  /* USER CODE BEGIN 5 */ 
  switch (Cmd)
  {
  case DFU_MEDIA_PROGRAM:
 
    break;
    
  case DFU_MEDIA_ERASE:
  default:
 
    break;
  }                             
  return  (USBD_OK);
  /* USER CODE END 5 */  
}
 
// 다음과 같이 수정
uint16_t MEM_If_GetStatus_FS (uint32_t Add, uint8_t Cmd, uint8_t *buffer)
{
  /* USER CODE BEGIN 5 */ 
    uint16_t FLASH_PROGRAM_TIME = 50;  
    uint16_t FLASH_ERASE_TIME = 50;  
  switch (Cmd)
  {
  case DFU_MEDIA_PROGRAM:
        buffer[1= (uint8_t)FLASH_PROGRAM_TIME;  
        buffer[2= (uint8_t)(FLASH_PROGRAM_TIME << 8);  
        buffer[3= 0;  
    break;
    
  case DFU_MEDIA_ERASE:
  default:
        buffer[1= (uint8_t)FLASH_ERASE_TIME;  
        buffer[2= (uint8_t)(FLASH_ERASE_TIME << 8);  
        buffer[3= 0;  
    break;
  }                             
  return  (USBD_OK);
  /* USER CODE END 5 */  
}
cs


설명할 내용이 많아서 다음과 같이 3번에 나눠서 글을 써야겠네요.

1/3 : DFU-Bootloader 툴 설정
2/3 : DFU-Bootloader 프로그램 수정
3/3 : DFU-Bootloaderble APP 프로그램 수정

오늘은 1/3 : DFU-Bootloader 툴 설정 에 관해서 글을 써 보겠습니다.

STM32F4xx 중에서 제가 테스트한 MCU는 STM32F411 과 STM32F446 입니다.
두 디바이스 모두, DFU(Device Firmware Upgrade) 동작이 잘 됩니다.

DFU는 2가지 방법으로 구현을 할 수 있는데,
1. Embeded DFU mode : MCU 내부에 이미 들어있는 기능으로 평소에 JTAG로 동작하던 일반적인 펌웨어를 특정 Flash Memory 주소(0x08000000)에 다운로드할 수 있고,다음과 같은 특정한 핀 설정으로 진입한다. 초기 부팅 시 Boot1 핀은 PB2 핀과 기능을 공유한다.



2. Custom DFU Mode : MCU Flash 메모리에 User가 필요에 따라서 각종 DFU 진입 조건(사용자가 일반 핀입력이나 여러가지 조건으로 지정할 수 있다)과 APP Flash Memory 시작 주소를 수정할 수 있다. APP 프로그램의 설정을 수정해야 하는 번거러움이 있다.


제가 Embeded DFU를 사용하지 않고 Custom DFU를 사용하려 하는 이유는,
APP f/w 가 Flash MEM 의 최초 시작 주소부터 프로그램의 기능에 따라 늘어나는데, 얼마나 크게 만들어 질지 몰라서 
1. Flash data Memory(EEPROM Emulation 기능)를 위치시킬 주소를 정하기가 번거롭고,

여유롭게 뒤쪽에 있는 Sector에 data Flash를 위치 시키려고 보니,
뒤쪽으로 갈수록 Sector 당 Flash MEM 크기가 너무 큰 단위로 증가를 해서 버리는 메모리가 많았습니다.
Flash 메모리를 쓸려면 일단 Erase 해야 하는데, Erase 방법이 Setor 별로 지우던가 전체 메모리를 지워던가 2가지밖에 없었습니다.
그래서 또 다음과 같은 불합리한 문제가 있어서 Custom DFU를 사용하게 되었습니다.
2. 뒤로 갈수로 Sector 당 Flash MEM 크기가 증가하여 낭비되는 메모리가 많다.
3. Sector 당 메모리가 크면 1 sector를 지우는데 시간이 많이 걸린다.

STM32F411 과 STM32F446 은 플래쉬 메모리의 크기에 따라 2가지(256KB,512KB)로 나뉩니다.
그에 따른 Sector 구조는 다음과 같습니다.







Necleo 보드는 USB 콘넥터가 보드내에 없기 때문에 DFU 테스트를 위해서는 안쓰는 USB 케이블을 하나 잘라서 연결을 해야 합니다. 다음은 제가 만들어 붙인 USB 케이블 모습입니다.



이제 DFU를 테스트하기 위한 하드웨어는 다 준비가 됐기 때문에, CUBE-MX 툴로 프로그램을 만들어 보겠습니다.
예전에 쓴 글에 MCU 설정은 많이 나와 있으므로 바로 PINOUT 설정 내용을 그림으로 올리겠습니다.
1. DFU용 USB-FS 핀 2개
2. 외부 CLOCK 입력 핀 : 4개
3. 디버깅용 UART2(Necleo 보드의 JTAG IC에 의해 USB-to-UART 와 연결되어 있다)



클럭 설정은 USB를 사용하기 때문에 USB CLK 를 48MHz로 맞추다 보니, System Clk은 96MHz로 설정된다.
System Clk 에 STM32F411의 최대 클럭인 100MHz 를 입력하면 자동으로 최적의 클럭을 계산해 준다.





기본으로 위와 같이 Disable 로 설정이 안되어 있어서 얼마전에 Custom DFU가 안되는 줄 알고 한참 해멨는데, 주의해야 겠습니다.

 

다음으로 CUBE MX툴의 Config 탭의 USB DEVICE 버튼을 클릭해서 파라메터를 설정합니다.





여기서 눈여겨 볼 내용은 2가지 입니다.
1. USBD_DFU_APP_DEFAULT_ADD : 나중에 업로드할 APP f/w 의 Flash Memory Start Address 를 입력합니다.
Sector 단위로 구분하여 App f/w 를 위치시킬 Sector의 시작 주소를 넣어 줍니다. 저는 Sector 2 부터 사용하려고 0x08008000을 입력했습니다.
2. USBD_DFU_MEDIA Inteface : 부트로더 프로그램의 시작 주소와 각각의 메모리의 Sector 별 특징을 넣어 줍니다.
이 파라메터는 꽤 복잡하게 구성되어 있는데, 해당 입력란을 클릭하면 아래 설명이 나와 있습니다.

USBD_DFU_MEDIA Interface USBD_DFU_MEDIA Parameter Description: The description of the flash (used by PC tool DFuSe) Each Alternate setting string descriptor must follow this memory mapping so that the PC Host Software can decode the right mapping for the selected device: ● @: To detect that this is a special mapping descriptor (to avoid decoding standard descriptor) ● /: for separator between zones ● Maximum 8 digits per address starting by “0x” ● /: for separator between zones ● Maximum of 2 digits for the number of sectors ● *: For separator between number of sectors and sector size ● Maximum 3 digits for sector size between 0 and 999 ● 1 digit for the sector size multiplier. Valid entries are: B (byte), K (Kilo), M (Mega) ● 1 digit for the sector type as follows: – a (0x41): Readable – b (0x42): Erasable – c (0x43): Readable and Erasabled (0x44): Writeable – e (0x45): Readable and Writeable – f (0x46): Erasable and Writeable – g (0x47): Readable, Erasable and Writeable Note: If the target memory is not contiguous, the user can add the new sectors to be decoded just after a slash"/" as shown in the following example: "@Flash /0xF000/1*4Ka/0xE000/1*4Kg/0x8000/2*24Kg"

제가 위의 내용을 토대로 그림으로 좀 정리를 해 봤습니다.



이 파라메터는 나중에 DFU F/W Upgrade 툴인 DfuSe 프로그램에서 읽어서 사용하게 됩니다.
어떻게 사용되는지 미리 알아 보겠습니다.



다음은 DFU 모드로 들어가기위해, 여러가지 조건들을 만들어 넣을 수 있겠지만 Necleo 보드의 User 스위치를 누르면 진입하도록 GPIO 설정에서 핀내부 풀업해 줍니다. (스위치가 누르면 GND와 붙기 때문에)



인터넷에서 DFU 관련 사이트를 찾아보니 HEAP 크기를 늘려 주라고 되어 있었는데, 왜 그런지는 모르겠습니다.
예로써, 0x2000 크기를 Heap으로 잡아 놨던데, 실험 결과 0x800 만 잡아서 문제는 (아직까지) 없었습니다.
Heap 을 늘리면 RAM 의 ZI(Zero Initialize Data)가 늘어 나는 것이 차이라면 차이랄까?



여기까지 CUBE MX툴에서 할 일은 모두 끝났습니다.
다음 단계로 KEIL 컴파일러용 코드를 만들어서 JTAG로 프로그램을 다운로드하고 리셋을 눌러서 부트로더를 실행해서 윈도우의 장치관리자에서 STM Device in DFU Mode 가 뜨면 성공입니다.

아직 프로그램을 추가하고 수정해야 하지만, 이 상태로 STM Device in DFU Mode 가 떠야 그 다음으로 넘어갈 수 있습니다.





회사 일이 많이 바빠서 테스트는 다 했지만 글을 못 올리네요. 약간 설명할 내용이 많아서 시간을 내기가 더 힘든 것 같습니다.
곧 프로그램 수정방법도 올려 보겠습니다.

궁금하신 분은 KEIL 소스코드를 올려 놓을 테니, 미리 보고 공부하셔도 됩니다. ^^


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


며칠동안 STM32F446 으로 DFU Custom Bootloader 를 열심히 테스트해 봤는데,


결과는 안됐습니다.

STM32F446 은 기본으로 DFU 부트로더가 내장되어 있어서,
BOOT1,BOOT0 을 High로 하고 리셋을 하면 USB로 펌웨어를 다운로드 할 수 있고 동작을 확인해 봤습니다.

그런데 Custom으로 DFU를 구현하는데에는 실패했는데,
STM32F446 이 안되는 것 같습니다.

STM32F407 로 되는 소스를 중국 사이트에서 보고 그대로 했고, 구글 사이트에서도 보고 따라했는데..
다들 문제가 없는 것 같은데 잘 안되네요.

CUBE MX 툴로 프로그램을 하고 있는데, CUBE MX 툴이 다루기는 쉬운데 아직 버그가 많다고 합니다.

지난 번에도 STM32F446 으로 DMA mem to GPIO 를 동작 시키는 데에도 잘 안 됐던 것도 
제 생각에는 CUBE MX 툴이 의심이 갑니다.

다음번에 STM32F411로 한번 해 볼 예정입니다.


역시 CUBE MX 버그인 것 같습니다. 
STM32F411 에서는 똑같은 프로그램인데, DFU 모드로 진입합니다.
아.. 이거 언제 고쳐질런지. (mem-to-GPIO DMA 와 Custom USB DFU)

원인 찾았습니다. 죄송합니다. 아래 댓글에 그동안 안됐던 내용 적어놨습니다.
STM32F446 CUBE MX 툴 이상 없습니다. 내일이나 모레 DFU 부트로더 프로그램에 대한 내용을 다루겠습니다.


STM32F411에서 USB DFU 드라이버가 장치관리자에 설치된 화면은 다음과 같습니다.




STM32F411 에서는 SDIO DMA 로 SD-Card가 잘 읽혔습니다.


방법이 어렵지 않아서 금방 테스트가 끝났는데, STM32F446 에서는 DMA를 사용하면 f_mount() 함수에서 멈춰버렸습니다.
DMA를 사용할 경우 sd_diskio.c 의 SD_read 함수 내의 
BSP_SD_ReadBlocks() 함수를 BSP_SD_ReadBlocks_DMA() 함수로 바꿔주면 됩니다.
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
/**
  * @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)
  {
    res = RES_ERROR;
  }
  
  return res;
}
 
/**
  * @brief  Writes Sector(s)
  * @param  lun : not used
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to write (1..128)
  * @retval DRESULT: Operation result
  */
#if _USE_WRITE == 1
DRESULT SD_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
{
  DRESULT res = RES_OK;
  /*if(BSP_SD_WriteBlocks((uint32_t*)buff, 
                        (uint64_t)(sector * BLOCK_SIZE), 
                        BLOCK_SIZE, count) != MSD_OK)*/
  if(BSP_SD_WriteBlocks_DMA((uint32_t*)buff, 
                        (uint64_t)(sector * BLOCK_SIZE), 
                        BLOCK_SIZE, count) != MSD_OK)
  {
    res = RES_ERROR;
  }
  
  return res;
}
cs


STM32F446 Nucleo 보드에서 DMA를 사용하지 않으면 동작은 잘 되는데, 
가끔 Error이 발생해서 SD-Card 와의 선을 짧게 하니 잘 동작 했습니다.
보드의 아트웍 영향인건지는 확실히 모르겠는데, 배선도 좀 짧으면 좋은 것 같습니다.



이 상태로 13KByte 의 데이터를 읽는데 10ms 정도의 시간이 걸렸으니 
거의 1MB/sec 의 속도밖에 안나와서 SDIO를 왜 쓰나 싶더군요.

그래서 처음부터 다시 프로젝트를 만들어서 동작을 시켜봤는데, 역시나 결과는 DMA 기능은 구현이 안되네요.

그런데, 뭔가 틀려진 점이 있는데, DMA를 안 썼을 때 13KB를 읽는데 10ms 걸렸던 것이 2ms 로 대폭 줄었습니다.
24KB를 읽었더니 3ms 고요. 마치 DMA가 동작되는 듯이 느껴질 정도로 빨라졌지만 DMA는 동작되지 않는 상황.
왜 빨라졌는지 원인은 못 찾았지만 CUBEMX 툴이 아직 안정화가 덜 됐나 봅니다.

빨라진 이유는 제가 코드를 잘 못 만들어서 그렇네요. f_read()함수가 512 Byte씩 읽어야 되는 줄 알고 24K 파일을 
512Byte 단위로 f_read()함수를 이용해 여러번 나눠서 읽었더니 느리고 한번에 24KB를 다 읽으니 빠르네요.
예전에 테스트를 했을 때, 512-Byte 이상 읽으면 안 읽혔었는데 이번에 해 보니 한번에 많은 데이터도 읽기 가능하네요. 

[512 Byte 씩 여러번 읽어서 느렸던 코드내용]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
uint8_t read_f_dat(uint8_t *pf_dat,FIL* fp)
{
    UINT i,testBytes;
    uint32_t f_size;
 
    f_size = 13000;
 
    // #define _MAX_SS 512
    
    for (i=0;i<f_size;i+=_MAX_SS)
    {
        if ((f_size - i) < _MAX_SS)
        {
            res = f_read(fp, &pf_dat[i],(f_size - i), &testBytes);
        }
        else
        {
            res = f_read(fp, &pf_dat[i],_MAX_SS, &testBytes);
        }
    }
}
cs

[한번에 많은 데이터를 읽어서 빨라지도록 수정된 코드]
1
2
3
4
5
6
7
8
9
uint8_t read_f_dat(uint8_t *pf_dat,FIL* fp)
{
    UINT testBytes;    
    uint32_t f_size;
 
    f_size = 13000;
    
    res = f_read(fp, &pf_dat[0],f_size, &testBytes);
}
cs

아뭏든 현재 첨부한 프로젝트는 DMA는 동작이 안되는 것으로 보이고, 계속해서 방법을 찾아보다가 
잘 되면 DMA SDIO-4bit 관련 글을 다시 올려 보겠습니다.


다음은 Nucleo - STM32F446 보드에서 DMA가 안되서 새로 프로젝트를 만들었던 과정입니다.

1. 먼저 MCU를 STM32F446로 선택하고, SDIO,UART2,GPIO 1개 출력(오실로 스코프로 시간이 얼마나 걸리는지 체크를 위해)



2. SDIO 신호와 u-SD-Card를 점퍼선으로 연결.



3. SDIO 를 DMA 로 동작하도록 CUBEMX 에서 설정.



4. sd_diskio.c 파일에서 SD_read/SD_write 함수 안의 BSP_SD_ReadBlocks/BSP_SD_WriteBlocks 함수를 바꾸지 않고 그대로 사용했는데 24KB 읽는데 걸리는 시간이 3ms 걸렸음.



5. 혹시 다 읽힌 것 맞는지 알아보려고 해리포터 소설을 UART로 뿌려보니 제대로 읽혀 있었습니다.
DMA가 동작한 것으로 믿어야겠죠? (제가 미쳤나 봅니다. 이런 상황을 가지고 DMA가 된다고 생각했다니.. 사기꾼이 될 뻔한 것을 글을 다시 보고 수정하면서 정정합니다.)
SDIO는 많은 양을 읽을수록 속도가 점점 더 빨라지는 듯 합니다.

그리고 STM32F411 은 42MHz 로도 DMA가 동작하는데, STM32F446 은 45MHz로 일반 방식에서는 동작하지 않았습니다.
(차후 DMA 구현되면 다시 비교해 볼 예정)

[STM32F4xx 에서 SDIO CLK를 최대(411:42MHz,446:45MHz)로 설정한 코드 내용]
1
2
3
4
5
6
7
8
9
10
11
12
/* SDIO init function */
static void MX_SDIO_SD_Init(void)
{
 
  hsd.Instance = SDIO;
  hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
  hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_ENABLE;
  hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
  hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
  hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
  hsd.Init.ClockDiv = 0;
}
cs

STM32F446 이 DMA가 안된 상태에서 비교를 하니 의미가 많이 없지만, 
또한 411은 SDIO_CLK=42MHz 이고 446은 22.5MHz 이기때문에, 
이 상태에서는 (당연하게도)STM32F411 이 더 빨랐습니다. 

STM32F446(SDIO_CLK:22.5MHz)은 13KB 읽는데 2ms, 24KB 3ms가 걸렸는데, 
STM32F411(SDIO_CLK:42MHz) 은 13KB/1.4ms , 24KB/2ms 밖에 안걸립니다. 

24KB 읽는 것을 기준으로 STM32F411 은 최대 속도 12MB/s , STM32F446 은 최대속도 8MB/s 나오네요.

이 속도는 앞서서도 말씀 드렸듯이 한번에 많이 읽으면 더 빨라집니다.

6. 해리포터 소설을 24KB 만큼 읽어서 출력해 봄.



7. 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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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_DMA_Init();
  MX_SDIO_SD_Init();
  MX_USART2_UART_Init();
  MX_FATFS_Init();
 
  /* USER CODE BEGIN 2 */
    read_TAR_file_DMA();
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
 
  }
  /* USER CODE END 3 */
 
}
 
/* SDIO init function */
static void MX_SDIO_SD_Init(void)
{
 
  hsd.Instance = SDIO;
  hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
  hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
  //hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_ENABLE;
  hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
  hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
  hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
  hsd.Init.ClockDiv = 0;
 
}
 
/** 
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();
 
  /* DMA interrupt init */
  /* DMA2_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 10);
  HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
  /* DMA2_Stream6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 20);
  HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn);
 
}
 
/* USER CODE BEGIN 4 */
void read_TAR_file_DMA(void)
{
     #if _USE_LFN
    TCHAR lfn[_MAX_LFN + 1];
    fno_t.lfname = lfn;
    fno_t.lfsize = sizeof lfn;
    #endif
    
    BYTE testBuffer[24000];
   uint8_t path[13];            
 
    DWORD cnt_rd_Byte=24000;
  UINT testBytes;
    DWORD cnt_i;
    UINT i,cnt_512;
    uint32_t image_offset;
 
 
    //sprintf(path,"1.tar");
    sprintf(path,"aaa.txt");
    printf("%s\n\r",path);
    
    res_t = f_mount(&fs32_t,SD_Path,1);
    printf("SD Mount : res f_mount : %02X\n\r",res_t);
    res_t = f_open(&fil_t, (char*)path, FA_READ); 
    printf("res f_open : %02X, fil_t.fsize : %d\n\r",res_t,fil_t.fsize);
 
    printf("Read Txt File Test\n\r");
    
    GPIOB->ODR ^= GPIO_PIN_5;    
    res_t = f_read(&fil_t, testBuffer, cnt_rd_Byte, &testBytes);
    GPIOB->ODR ^= GPIO_PIN_5;    
    printf("res_t = f_read : %02X\n\r",res_t);
    
    for(i=0;i<24000;i++)
        printf("%c",testBuffer[i]);
}
 
/* USER CODE END 4 */
 
cs



추가로 DMA 인터럽트도 DMA 동작이 안되는 것과 관련이 있을 수도 있다 싶어서 인터럽트 설정 부분도 올려 봅니다.
혹시 왜 DMA가 안되는지 아시는분 알려주시면 대단히 고맙겠습니다.

아래와 같이 인터럽트를 여러가지로 변경해 봤는데, DMA 동작은 여전히 안됐습니다. 
그런데 DMA를 사용하지 않아도 SDIO_CLK 22.5MHz의 속도로써 늦은 속도는 아닌 것 같습니다.
(STM32F411 SDIO_CLK 42MHz(DMA)에서 13KB 전송시 1.4ms 이고, STM32F446 SDIO_CLK 22.5MHz(No DMA) 에서 2ms 걸렸기 때문에)
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
/** 
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();
 
  /* DMA interrupt init */
  /* DMA2_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 20);
  HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
  /* DMA2_Stream6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 30);
  HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn);
}
 
static void MX_DMA_Init(void
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();
  /* DMA interrupt init */
  /* DMA2_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 10);
  HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
  /* DMA2_Stream6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 20);
  HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn);
}
 
static void MX_DMA_Init(void
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();
  /* DMA interrupt init */
  /* DMA2_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 00);
  HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
  /* DMA2_Stream6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 00);
  HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn);
}
cs



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

sdio_pure_v1.zip


메뉴얼에서는 못 찾았는데, 구글 검색해 보니 잘 나와 있네요.


이 내용의 원문은 다음과 같습니다.

아무리 메뉴얼을 뒤져봐도 못 찾았던 내용인데, 이 사람은 어디서 찾은 걸까요?
제가 꼭 알고 싶었던 내용입니다. 
타이머를 쓰긴 쓰는데, Clock 소스가 180 MHz 인지, 90MHz 인지, 45MHz 인지 통 모르겠드만...
그래서 오실로 스코프로 찍어보고 알아야만 했거든요. ㅜㅜ

맨 마지막에 APB 번호가 나와 있어서 CubeMX 툴의 Clock Configuration 과 비교해 보면 알 수 있습니다.




TIM2 는 이전에 해봐서 90MHz가 Clock Source 라는 것을 알았고, 만약 TIM1 으로 테스트 하면 Clock Source가 180MHz 겠군요.

클럭은 Cube Mx로 다음과 같이 설정하고,





소스 코드는 아래와 같이 TIM1 Update 인터럽트가 걸릴때 마다 PA5를 토글하도록 만들어 봤습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
 if (htim->Instance == TIM1)
 {
  GPIOA->ODR ^= GPIO_PIN_8;
 }
}
 
int main(void)
{
  HAL_Init();
 
  SystemClock_Config();
 
  MX_GPIO_Init();
  MX_TIM1_Init();
 
  HAL_TIM_Base_Start_IT(&htim1);
 
  while (1)
  {
  }
}
cs


그 결과 PA5 를 오실로 스코프로 찍어 보니, 위의 표와 계산한 값과 같이 10ms 마다 토글하도록 출력이 됩니다.




[이 프로젝트에 사용된 MCU는 NUCLEO-F446RE(STM32F446RET6) 입니다.]


제가 지금까지 타이머 인터럽트를 안다뤘더군요. 

가장 많이 쓰는 기능일텐데, 이제서야 여러 기능들을 합쳐서 프로그램을 짜 넣으려고 하다 보니 빠진 것을 알았습니다.

먼저 CubeMx 툴을 실행.
TIM2 의 Clock Source 를 Internal Clock으로 하여 기능을 살려 놓습니다. 
그리고 TIM2 Update 인터럽트가 제대로 걸리고 있는지 확인을 위해 포트를 토글하려고 PA5를 GPIOOUT으로 설정.



다음은 클럭 설정으로 가서 STM32F446의 시스템 클럭을 180MHz로 가장 높게 맞췄습니다.
참고로 메뉴얼을 봐도 TIM2 의 클럭 소스가 어떤 것인지 몰라서 직접 실험으로 알아본 결과 APB1 Timer Clock(90MHz) 네요.
누가 혹시 이 내용에 대해 자세히 나온 글이 있는지 아시면 알려 주시면 감사하겠습니다. (전 도저히 못 찾겠네요)



configuration 탭으로 가서 TIM2 의 세부 설정을 위해 TIM2를 클릭.



10ms 마다 TIM2 Update 인터럽트가 걸리게 하기 위해, Parameter 설정 탭으로 가서 Period 를 900,000 으로 설정했습니다.
공식은 period count = 0.01(sec) x 90Mhz = 900,000 입니다.



인터럽트를 쓸 것이기 때문에 NVIC Setting으로 가서 인터럽트를 인에이블 체크를 합니다.



메뉴 중, project->Generate Code 를 눌러 컴파일러에 맞게 코드를 생성합니다.



1. HAL_TIM_Base_Start_IT 함수로 타이머 업데이트 인터럽트가 걸리도록 합니다.
2. stm32f4xx_hal_tim.c 의 __weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) 함수에서 
--weak 를 제거하고 main.c 에 옮겨서 TIM2 update 인터럽트가 걸릴때 실행될 코드를 추가하거나 수정합니다.
TIM2 update 인터럽트가 걸릴때 HAL_TIM_PeriodElapsedCallback 함수를 호출하도록 되어 있지요.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
  * @brief  Period elapsed callback in non blocking mode 
  * @param  htim: pointer to a TIM_HandleTypeDef structure that contains
  *                the configuration information for TIM module.
  * @retval None
  */
__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(htim);
  /* NOTE : This function Should not be modified, when the callback is needed,
            the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file
   */
}
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
33
34
35
36
37
38
39
40
41
42
43
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
   */
}
 
 
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_TIM2_Init();
  MX_TIM1_Init();
  MX_USART2_UART_Init();
 
  /* USER CODE BEGIN 2 */
  printf("UART Test...\n\r");
    
  HAL_Delay(500);
  HAL_TIM_Base_Start_IT(&htim2);
    
  while (1)
  {
            HAL_GPIO_TogglePin(LED_OUT_GPIO_Port,LED_OUT_Pin);
  }
 
}
cs


그런데 주의할 점은 어떤 Timer 인터럽트가 실행되도 호출되는 callback 함수는 HAL_TIM_PeriodElapsedCallback 입니다.
따라서, HAL_TIM_PeriodElapsedCallback 함수 내에서 어떤 타이머에서 날 불렀는지 확인할 필요가 있습니다.

그래서 다음과 같이 확인하는 코드가 필요합니다.





즉 날 호출한 게, TIM1 이냐? TIM2 냐? 이런 뜻이죠. 
저는 TIM2에 의해 호출되도록 만들었기 때문에, TIM2에 의해 호출되면 A5 핀을 토글하도록 해서 결과는 다음과 같습니다.





정확히 10ms 마다 포트가 토글 되네요.
동작 잘 되네요~~^^

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


[이 프로젝트에 사용된 MCU는 NUCLEO-F446RE(STM32F446RET6) 입니다.]

SPI 통신을 DMA로 송신했을 시, 파형이 기존 SPI에 비해 끊김 없이 최대 45MBit/s 로 나옵니다.

DMA를 안 쓰면 데이터 Rate가 절반도 안나왔는데, DMA로 하면 효율이 100% 입니다.

SPI tx 를 DMA로 보냈을 때 파형이 어떤지만 간단하게 설명하고 끝낼 예정입니다. ^^



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

SPI_DMA.zip


+ Recent posts