[이 프로젝트에 사용된 MCU는 NUCLEO-F446RE(STM32F446RET6) 입니다.]
이번에는 memory to GPIO 를 DMA로 출력하는 테스트를 할 예정입니다.

타이머 1개를 트리거 신호로 사용하여 65535개까지 연속으로 데이터를 출력할 수 있으며,
속도는 최대 22.7MHz 까지 나옵니다.

8 비트 데이터 버스로 사용한다면 대략 180Mbit/s 의 속도가 나오므로 꽤 나오죠?
저는 동시에 여러개의 SPI 버스의 데이터를 송신하는 용도로 사용하려고 테스트를 진행중입니다.
(예전에 테스트를 어느정도 해 놓고, 정리 차원에서 카페에 글을 올리는 중입니다.)

예전에 테스트한 보드는 Nucleo STM32F411 보드에서 했었고,
이번에는 Nucleo STM32F446 보드에서 테스트할 예정입니다.

참고로 한 내용은 st 홈페이지의 AN4666 입니다.

예제 프로그램을 위의 링크된 사이트에서 다운 받을 수 있는데, 회원을 가입해야 했던 것 같습니다.

STM32F476G Discovery 보드의 예제인데, TIM2 의 CH1을 DMA 트리거 소스로 프로그램 되어 있습니다.
하지만 STM32F411,STM32F446 에서는 TIM2/CH1 을 DMA 소스로 했을 때 트리거 신호가 나오지 않는 문제가 발생했습니다.

아무리 해도 안되서 TIM1/CH1 을 트리거 소스로 사용하니 트리거 신호가 동작했습니다.

원인은 모르겠으나, TIM1 과 TIM2 의 큰 차이는 Repetition Count 가 있고 없고의 차이인데 확실히 트리거 신호가 안나오는 이유는 모르겠지만, TIM1/CH1 은 동작합니다. (원인을 알고 계시면 알려 주시면 감사하겠습니다)

다음은, STM32CubeMX 툴로 프로젝트를 만드는 과정을 그림으로 올려 보겠습니다.

먼저 Pinout 탭에서 사용할 기능을 설정합니다.
NuCleo 보드에서 클럭을 8MHz Bypass 클락과 32.768 KHz 크리스탈을 사용하므로 RCC를 그에 맞게 설정하고,
트리거 소스로 사용할 TIM1 의 Clock Source 를 Internal Clock, 출력 채널을 PWM Generation CH1 으로 설정합니다.
이 때, GPIO 출력으로 사용할 포트는 CubeMX 툴에 표시되지 않습니다. (나중에 알았는데, GPIO OUT 으로 설정하면 녹색으로 표시 된답니다. 속성으로 하다 보니, 모르는 것이 많네요.^^)



실제 Nucleo 보드의 출력핀은 다음과 같습니다.



다음은 큐브 툴에서 Clock을 설정합니다. 입력 클럭 8MHz,32.768 KHz 클럭을 소스로 시스템 클럭은 STM32F446 의 최대 클럭인 180MHz 로 설정을 했습니다. 180MHz 를 쓰고 엔터키만 치만 나머지를 자동으로 맞춰주니 골치아파할 필요가 없습니다.



다음은 TIM1 주기와 듀티 및 DMA 설정을 위해서 CubeMX 툴의 configuration 탭을 선택하고 TIM1 을 클릭합니다.



parameter Setting 에서 제 경험상 가장 빠른 출력 주기인 7로 주기를 설정하고, High 로 유지할 펄스는 2로 설정합니다.
주기를 더 빠르게 하면 트리거 출력이 나오다 없어지거나 처음부터 안나옵니다.



다음은 DMA Setting 입니다.
수정할 부분은 Direction 을 Memory to Peripheral 로 고치고, 저는 Data Width를 Byte로 해서 출력 하려고 Byte로 설정했습니다.



이렇게 하고 나서 Keil compiller 프로젝트 형식으로 소스 코드를 만들기 위해 project/Setting 메뉴에서 Toolchain / IDE 옵션을 MDK-ARM V5 로 선택합니다.



그런 다음, project/Generate Code 를 클릭하면 Keil 프로젝트가 만들어 집니다.



main.c 의 main() 함수에 추가한 내용은 아래 붉은색 네모의 내용이 다 입니다.



그런데 가장 중요한 부분은 stm32f4xx_it.c 에 있는 DMA IRQHandler 함수에 TIM1 을 멈추게 하는 코드를 넣어야 한다는 점입니다.
저는 예제를 받아서 수정을 한 것이 아니고, 참고로 해서 CubeMX 툴에서 다시 만들었기 때문에 main.c 파일만 보고 수정된 부분을 비교해서 수정을 했기 때문에 stm32f4xx_it.c 파일을 수정해야 한다는 생각을 못하고 프로그램을 실행하니 아무리 해도 동작이 안되었습니다. 원인은 이 코드를 수정하지 않아서 트리거 클락이 끝없이 나가는 것이 문제였습니다.

그리고 트리거 클럭은 앞뒤로 더미 클럭이 나가니, GPIO를 사용할 경우 주의하시기 바랍니다. SPI로 동작 시킬 경우에는 데이터에 /CS 데이터를 실어서 출력하면 더미데이터를 신경 안 쓰고 사용할 수 있을 것 같습니다.
더미 데이터는 클럭이 빠를수록 더 많이 나옵니다.



다음은 첨부된 프로젝트 파일의 실행 결과 입니다.



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

mem_t_GPIO_v1.zip


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


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

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

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




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



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

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

DATA[1:0] : PC7,6

SYNC CLK : PB5


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

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


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

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


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


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


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

  {

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

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

  }


  while (1)

  {

par_ReAssemble(par_data,8);

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

HAL_Delay(1);

  }




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

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

void par_ReAssemble(uint8_t *par_data,uint16_t cnt_data)

{

uint16_t i;

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

{

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

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

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

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

//HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5,GPIO_PIN_SET);

//HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5,GPIO_PIN_RESET);

GPIOB->BSRR = GPIO_PIN_5;

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

}

}


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

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

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


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

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

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5,GPIO_PIN_SET);

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

GPIOB->BSRR = GPIO_PIN_5;

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


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

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




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

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

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


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



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



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

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


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


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



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

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


[ SYNC_CLK 와 DATA 0,1,2 ]



[ SYNC_CLK 와 DATA 2,3,4 ]



[ SYNC_CLK 와 DATA 5,6,7 ]



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


그럼 이만 마치겠습니다.


소스 코드 첨부 합니다.

perallel_GPIO.zip



이번에는 GPIO 출력 방법에 대해서 알아 보겠습니다.


GPIO 출력을 위해서는 먼저 GPIO 를 초기화 해야하는데, 
STM 계열의 문법은, 먼저 다음과 같은 구조체로 GPIO 변수를 선언하는 군요.

main.c 파일에서 global 구조체 함수로 다음과 같이 정의를 합니다.
static GPIO_InitTypeDef  GPIO_InitStruct;

GPIO_InitTypeDef 라는 구조체 형식은 다음과 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/** 
  * @brief GPIO Init structure definition  
  */ 
typedef struct
{
  uint32_t Pin;       /*!< Specifies the GPIO pins to be configured.
                           This parameter can be any value of @ref GPIO_pins_define */
 
  uint32_t Mode;      /*!< Specifies the operating mode for the selected pins.
                           This parameter can be a value of @ref GPIO_mode_define */
 
  uint32_t Pull;      /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
                           This parameter can be a value of @ref GPIO_pull_define */
 
  uint32_t Speed;     /*!< Specifies the speed for the selected pins.
                           This parameter can be a value of @ref GPIO_speed_define */
 
  uint32_t Alternate;  /*!< Peripheral to be connected to the selected pins. 
                            This parameter can be a value of @ref GPIO_Alternate_function_selection */
}GPIO_InitTypeDef;
cs

제가 갖고 있는 Nucleo 보드에서 LED가 PA5 에 연결되어 있어서 핀을 출력으로 설정해 봤습니다.
main.c 파일 내에, uart 테스트에 사용했던 프로젝트에서 사용된 초기화 함수에 PA5 설정에 대한 코드를 추가했습니다.
다음의 빨간 색으로 된 부분이 추가된 코드입니다. 

__HAL_RCC_GPIOA_CLK_ENABLE() 함수는 원래 부터 uart 테스트 프로젝트에 있던건데 아마도 Uart 포트가 PA2,3 이라 포트를 사용하기 위해서 Clock Enable이 필요한 듯 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** Pinout Configuration
*/
static void MX_GPIO_Init(void)
{
 
  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();
 
  /* -2- Configure PA05 IO in output push-pull mode to
         drive external LED */
  GPIO_InitStruct.Pin = GPIO_PIN_5;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 
}
 
cs

다음으로 main()함수에 출력으로 설정된 LED GPIO 를 Toggle 하고 딜레이 함수로 100ms 기다리고를 반복하는 코드를 추가했습니다.
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
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();
 
  /* USER CODE BEGIN 2 */
 
    HAL_UART_Transmit(&huart2,"UART2 Test~!!! \n\r",17,0xFFFF);
//HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
    
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
    
    /* Insert delay 100 ms */
    HAL_Delay(100);
 
  }
  /* USER CODE END 3 */
 
}
cs

자동으로 리셋이 되지 않아서, 프로그램이 잘 안됐나 했는데...
리셋 버튼을 눌러주니 동작하기 시작하네요.

uart2 테스트와 gpio 테스트 파일을 첨부(공유)함니다.


+ Recent posts