본문 바로가기
# Semiconductor/- Semicon Academy

[Harman 세미콘 아카데미] 43일차 - ARM & RTOS 활용(Timer, EXTI, DMA)

by Graffitio 2023. 8. 25.
[Harman 세미콘 아카데미] 43일차 - ARM & RTOS 활용(Timer, EXTI, DMA)
728x90
반응형
[Timer]

 

Review

 

1. clock 발생 주기 계산법

Hz = clk / PSC*ARR

 


 

Timer Interrupt 활용

 

/* USER CODE BEGIN PV */
int xVal=0, yVal=0, zVal=0; // 조이스틱 및 버튼 상태 변수
int cnt = 0; // ADC 변환 카운트 변수
int dir = 0; // 방향 변수
// 방향 문자열 배열
char *dirs[] = {"Front      ", "Front-Left ", "Left       ", "Back-Left  ", "Back       " , "Back-Right ", "Right      ", "Front-Right", "Push", "    "};

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_ADC1_Init(void);
static void MX_TIM4_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

// UART 사용을 위한 putchar 함수
int __io_putchar(int ch)
{
   HAL_UART_Transmit(&huart2, &ch, 1, 100);
   return ch;
}

// 조이스틱 방향 계산 함수
void GetDirection()
{
         if(xVal > 3000)
         {
            if(yVal > 3000) dir = 5;
            else if(yVal >1000) dir = 6;
            else dir =7;
         }
         else if(xVal < 1000)
         {
            if(yVal > 3000) dir = 3;
            else if(yVal >1000) dir = 2;
            else dir = 1;
         }
         else
         {
            if(yVal > 3000) dir = 4;
            else dir = 0;
         }
}

// 조이스틱 테스트 함수
void Joystick_Test() //Read X and Y axis, convert to digital. zAxics also.
{
   int click_i;

   // ADC 변환 시작 및 값 읽기
   HAL_ADC_Start(&hadc1);
   HAL_ADC_PollForConversion(&hadc1, 100);
   xVal = HAL_ADC_GetValue(&hadc1);

   HAL_ADC_PollForConversion(&hadc1, 100);
   yVal = HAL_ADC_GetValue(&hadc1);

   HAL_ADC_Stop(&hadc1);

   // 버튼 상태 확인
   zVal = !(HAL_GPIO_ReadPin(Z_Value_GPIO_Port, Z_Value_Pin));

   if (zVal) click_i = 8;
   else click_i = 9;

   // 조이스틱 방향 계산
   GetDirection();

   // 화면에 정보 출력
   printf("x:%-5d y:%-5d z:%-3d  Direction : %-12s  Status : %-6s   \r\n", xVal, yVal, zVal, dirs[dir], dirs[click_i]);
}

// ADC 변환 완료 콜백 함수
void HAL_ADC_ConvCPpltCallback(ADC_HandleTypeDef* hadc) //ADC interrupt callback funtion
{

	int click_i;

   if(cnt == 0)
   {
      xVal = HAL_ADC_GetValue(&hadc1);
   }
   if(cnt == 1)
   {
      yVal = HAL_ADC_GetValue(&hadc1);
   }
   if(++cnt > 1)
   {
      zVal = HAL_GPIO_ReadPin(Z_Value_GPIO_Port, Z_Value_Pin);

      if (zVal) click_i = 8;
      else click_i = 9;

      GetDirection();

      printf("x:%d y:%d z:%d  Direction : %s  Status : %s   \r\n", xVal, yVal, zVal, dirs[dir], dirs[click_i]);
      cnt = 0;//once in Interrupt Service Routine
   }
   HAL_ADC_Start_IT(&hadc1);
}

// 일반 타이머 콜백 함수
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //General Timer handler cause not have word about timer4
{
   if(htim->Instance == TIM4) // TIM4 사용 시에만 Joystick_Test 함수 실행
         Joystick_Test();
}

// 외부 인터럽트 콜백 함수
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) // Generarl Callback
{
	if(GPIO_Pin == GPIO_PIN_7)
		Joystick_Test();
}

 

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();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_ADC1_Init();
  MX_TIM4_Init();
  /* USER CODE BEGIN 2 */
  //HAL_ADC_Start_IT(&hadc1); // ADC Interrupt Start
  HAL_TIM_Base_Start_IT(&htim4); // Timer Interrupt Start
  printf("Program Start!\r\n");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Timer Interrupt 발생 시, 출력됨.

 


 

[EXTI(외부 인터럽트)]

 

EXTI

 

PC7을 외부 인터럽트로 설정]

 

 

0~4 개별 인터럽트

5~9 그룹 인터럽트(그룹별로 관리함) ← PC7은 여기에 속함.

10~15 그룹 인터럽트(그룹별로 관리함)

 

외부 인터럽트에 해당하는 핸들러가 생성된 것을 확인할 수 있다.
어느 시점(Rising, Falling)에 동작할 지도 선택 가능

cf) Interrupt 와 Event의 차이점

    둘 다 컴퓨터 시스템 내에서 발생하는 외부 또는 내부적인 변화이지만, 의미와 사용법은 다소 다른 점이 있다.

    Interrupt

    : 컴퓨터 시스템에서 발생하는 외부 이벤트(주로 H/W적 관점)

      현재 실행 중인 작업을 중단하고 실시됨.

    Event

    : 컴퓨터 시스템 내에서 발생하는 모든 유형의 상황이나 변화(주로 S/W 적 관점)

      현재 실행 중인 작업을 중단하지 않고 실시됨.

 


 

EXTI(외부 인터럽트) 활용

 

풀업 저항으로 버튼 설계

 

/* USER CODE BEGIN PV */
int xVal=0, yVal=0, zVal=0; // 조이스틱 및 버튼 상태 변수
int cnt = 0; // ADC 변환 카운트 변수
int dir = 0; // 방향 변수
// 방향 문자열 배열
char *dirs[] = {"Front      ", "Front-Left ", "Left       ", "Back-Left  ", "Back       " , "Back-Right ", "Right      ", "Front-Right", "Push", "    "};

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_ADC1_Init(void);
static void MX_TIM4_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

// UART 사용을 위한 putchar 함수
int __io_putchar(int ch)
{
   HAL_UART_Transmit(&huart2, &ch, 1, 100);
   return ch;
}

// 조이스틱 방향 계산 함수
void GetDirection()
{
         if(xVal > 3000)
         {
            if(yVal > 3000) dir = 5;
            else if(yVal >1000) dir = 6;
            else dir =7;
         }
         else if(xVal < 1000)
         {
            if(yVal > 3000) dir = 3;
            else if(yVal >1000) dir = 2;
            else dir = 1;
         }
         else
         {
            if(yVal > 3000) dir = 4;
            else dir = 0;
         }
}

// 조이스틱 테스트 함수
void Joystick_Test() //Read X and Y axis, convert to digital. zAxics also.
{
   int click_i;

   // ADC 변환 시작 및 값 읽기
   HAL_ADC_Start(&hadc1);
   HAL_ADC_PollForConversion(&hadc1, 100);
   xVal = HAL_ADC_GetValue(&hadc1);

   HAL_ADC_PollForConversion(&hadc1, 100);
   yVal = HAL_ADC_GetValue(&hadc1);

   HAL_ADC_Stop(&hadc1);

   // 버튼 상태 확인
   zVal = !(HAL_GPIO_ReadPin(Z_Value_GPIO_Port, Z_Value_Pin));

   if (zVal) click_i = 8;
   else click_i = 9;

   // 조이스틱 방향 계산
   GetDirection();

   // 화면에 정보 출력
   printf("x:%-5d y:%-5d z:%-3d  Direction : %-12s  Status : %-6s   \r\n", xVal, yVal, zVal, dirs[dir], dirs[click_i]);
}

// ADC 변환 완료 콜백 함수
void HAL_ADC_ConvCPpltCallback(ADC_HandleTypeDef* hadc) //ADC interrupt callback funtion
{

	int click_i;

   if(cnt == 0)
   {
      xVal = HAL_ADC_GetValue(&hadc1);
   }
   if(cnt == 1)
   {
      yVal = HAL_ADC_GetValue(&hadc1);
   }
   if(++cnt > 1)
   {
      zVal = HAL_GPIO_ReadPin(Z_Value_GPIO_Port, Z_Value_Pin);

      if (zVal) click_i = 8;
      else click_i = 9;

      GetDirection();

      printf("x:%d y:%d z:%d  Direction : %s  Status : %s   \r\n", xVal, yVal, zVal, dirs[dir], dirs[click_i]);
      cnt = 0;//once in Interrupt Service Routine
   }

   HAL_ADC_Start_IT(&hadc1);
}

// 일반 타이머 콜백 함수
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //General Timer handler cause not have word about timer4
{
   if(htim->Instance == TIM4) // TIM4 사용 시에만 Joystick_Test 함수 실행
         Joystick_Test();
}

// 외부 인터럽트 콜백 함수
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) // Generarl Callback
{
	if(GPIO_Pin == GPIO_PIN_7)
		Joystick_Test();
}

 

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();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_ADC1_Init();
  MX_TIM4_Init();
  /* USER CODE BEGIN 2 */
  printf("Program Start!\r\n");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

버튼을 눌렀을 때만 출력됨.

 


 

[DMA]

 

DMA란?

 

Direct Memory Access의 약자로

컴퓨터 시스템에서 데이터 전송을 위해 CPU 개입 없이 하드웨어적으로 데이터를 복사하거나 이동하는 매커니즘이다.

DMA는 특히 대량의 데이터를 처리하는 경우, CPU의 부하를 줄이고 전송 효율을 높이는데 사용된다.

고속 데이터처리방법(CPU를 거치지 않고 바로 메모리에 꽂아버린다.)

 

일반적으로 CPU는 데이터를 Source에서 Destination으로 이동시키기 위해 중간 과정을 거친다.

예를 들어, CPU가 데이터를 읽고 데이터 버스를 통해 메모리로 전송한 다음, 다시 목적지에서 데이터를 읽어와야 한다.

이렇게 CPU가 개입하면 데이터 전송에 필요한 시간이 길어지고 CPU의 성능을 낮출 수 있다.

 

DMA는 이러한 문제를 해결하기 위해 CPU의 개입 없이 데이터 전송을 처리하는 기술이다.

DMA 컨트롤러는 CPU와 별도로 작동하며, CPU의 개입 없이 하드웨어 디바이스와 메모리 간 데이터 전송을 수행하고

DMA를 사용하면 CPU는 데이터 전송을 관리할 필요없이 다른 작업에 집중할 수 있다.

 


 

DMA 작동 방식

 

1. DMA 채널 설정

    : DMA 컨트롤러는 여러 개의 DMA 채널을 가지고 있으며,

      각 채널은 데이터 전송을 수행하는 독립적인 작업 단위이다.

 

2. DMA 요청 발생

    : 하드웨이 디바이스(ex : ADC, UART, Memory 등)는

      DMA 컨트롤러에 데이터 전송을 요청한다.

 

3. CPU의 개입 없이 데이터 전송

    : DMA 컨트롤러는 CPU의 개입없이 데이터를 읽거나 쓰는 작업을 수행한다.

      이 작업은 별도의 DMA 버스를 통해 수행된다

 

4. 전송 완료 인터럽트

    : 데이터 전송이 완료되면, DMA 컨트롤러는 인터럽트 신호를 발생시켜

      CPU에게 전송 완료를 알린다.

 


 

DMA Setting

 

DMA Resource만 생성해준 것.
Circular로 설정

DMA (Direct Memory Access) Circular mode는 데이터 전송을 원형 방식으로 처리하는 DMA 작동 모드이다.

DMA는 컴퓨터 시스템에서 메모리 간의 데이터 전송을 효율적으로 관리하기 위한 방법으로 사용되며,

DMA Circular mode는 이러한 데이터 전송을 무한히 반복하여 처리하는 모드이다.

Data Width 설정

    <Data Width>

        데이터 크기가 얼마냐?

        표준 데이터 크기는 8의 배수.

        정규적인 데이터의 범위를 맞추기 위해 앞에 4bit을 붙여 준다 

        그렇게 16bit를 만든다

        이렇게 만든 16bit짜리 데이터를 short(=Half Word)라고 부른다.

 

DMA를 사용할 것이므로 Enable, all conversion으로 변경
어느 타이머 트리거를 사용할 지, 어떤 Edge에서 동작시킬 지 설정
위에서 Out Event로 세팅해줬기 때문에 TIMER3에서는 Update Event로 설정
DMA 세팅을 완료하면, 위와 같이 DMA 전송 완료 interrupt가 생성된다.
Code Generation하면 이렇게 핸들 생성됨.

 


 

HAL_ADC_Start_DMA

 

 &hadc : UART Port에 대한 handle(포인터)

 pData : DMA로 사용할 메모리 버퍼

 Length : 바이트 단위의 크기

 


 

union으로 BUFFER 정의

 

/* 데이터 저장을 위한 BUFFER 구조체 정의 */
typedef union
{
    char cbuf[2];     // 크기가 100인 문자형 배열 cbuf 선언 (DMA 버퍼)
    uint16_t ibuf;  // 크기가 50인 16비트 부호 없는 정수형 배열 ibuf 선언
} BUFFER;                // typedef를 사용해 "BUFFER"를 구조체의 별칭으로 지정

/* BUFFER 타입의 배열 "bDMA"를 선언 */
BUFFER bDMA[100];       // "bDMA"는 100개의 BUFFER 유형을 저장하는 배열

/* 
   위 코드에서 정의된 구조체와 배열은 다양한 데이터 유형을 공유하며 활용할 수 있습니다.
   "cbuf" 배열은 문자 데이터나 8비트 이진 데이터를 저장하는 용도로 사용될 수 있으며,
   "ibuf" 배열은 16비트의 숫자 데이터를 저장하는 데 사용될 수 있습니다.
   "bDMA" 배열은 "BUFFER" 타입을 100개 저장할 수 있는 배열로, 다양한 데이터 형식을 저장하거나 처리하는 데 유용합니다.
   "bDMA" 배열의 각 요소(element)마다 cbuf[2], ibuf가 들어 있다.
    -> 따라서 cbuf[2]와 ibuf는 총 101개
*/

 

원하는 데이터 타입 선택 가능

위와 같이, 유니온 타입으로 설계하면, 들어온 값 그대로 쓰고 아무런 변환없이 다른 데이터 구조로도 쓸 수 있다.

 


 

DMA 활용

 

Background에서는 TIM3에 의해 Joystick의 ADC 값들이 입력이 되고(200ms에 한 번씩 입력됨)

위에서 입력된 값들은 while문에 작성선 printf에 의해 150ms에 1번씩 출력된다.

중간에 버튼 입력으로 인한 외부 인터럽트가 발생할 경우, Joystick_Test() 함수 출력

 

/* USER CODE BEGIN PV */

// 사용자 정의 타입인 BUFFER 선언
typedef union
{
	char cbuf[2]; // DMA 버퍼
	uint16_t ibuf; // 16비트 부호 없는 정수형 버퍼
} BUFFER;
BUFFER bDMA[100]; // BUFFER 타입의 배열 bDMA, 100개 요소
int xVal = 0, yVal = 0, zVal = 0; // 조이스틱 및 버튼 값 저장 변수
int cnt = 0; // ADC 변환 카운트 변수
int dir = 0; // 조이스틱 방향 인덱스

// 조이스틱 방향 문자열 배열
char *dirs[] = {"Front      ", "Front-Left ", "Left       ", "Back-Left  ", "Back       " , "Back-Right ", "Right      ", "Front-Right", "Push", "    "};

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_ADC1_Init(void);
static void MX_TIM4_Init(void);
static void MX_TIM3_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

// bDMA 변수를 이용한 사용자 정의 함수들

// 문자 출력을 위한 사용자 정의 putchar 함수
int __io_putchar(int ch)
{
   HAL_UART_Transmit(&huart2, &ch, 1, 100);
   return ch;
}

// 조이스틱 방향을 계산하는 함수
void GetDirection()
{
    if (xVal > 3000)
    {
        if (yVal > 3000) dir = 5;
        else if (yVal > 1000) dir = 6;
        else dir = 7;
    }
    else if (xVal < 1000)
    {
        if (yVal > 3000) dir = 3;
        else if (yVal > 1000) dir = 2;
        else dir = 1;
    }
    else
    {
        if (yVal > 3000) dir = 4;
        else dir = 0;
    }
}

// 조이스틱 값을 읽는 함수
void Joystick_Test()
{
    int click_i;

    HAL_ADC_Start(&hadc1);
    xVal = HAL_ADC_GetValue(&hadc1);
    yVal = HAL_ADC_GetValue(&hadc1);
    zVal = !(HAL_GPIO_ReadPin(Z_Value_GPIO_Port, Z_Value_Pin));

    if (zVal) click_i = 8;
    else click_i = 9;

    GetDirection();

    printf("x:%-5d y:%-5d z:%-3d  Direction : %-12s  Status : %-6s   \r\n", xVal, yVal, zVal, dirs[dir], dirs[click_i]);

    HAL_ADC_Start_IT(&hadc1);
}

// ADC 변환 완료 콜백 함수
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    int click_i;

    if (cnt == 0)
    {
        xVal = HAL_ADC_GetValue(&hadc1);
    }
    if (cnt == 1)
    {
        yVal = HAL_ADC_GetValue(&hadc1);
    }
    if (++cnt > 1)
    {
        zVal = HAL_GPIO_ReadPin(Z_Value_GPIO_Port, Z_Value_Pin);

        if (zVal) click_i = 8;
        else click_i = 9;

        GetDirection();

        printf("x:%d y:%d z:%d  Direction : %s  Status : %s   \r\n", xVal, yVal, zVal, dirs[dir], dirs[click_i]);
        cnt = 0;
    }

    HAL_ADC_Start_IT(&hadc1);
}

// 타이머 주기 만료 콜백 함수
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    //if(htim->Instance == TIM4) 
         //Joystick_Test();
}

// GPIO 인터럽트 콜백 함수
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	//if(GPIO_Pin == GPIO_PIN_7)
		Joystick_Test();
}

 

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();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  MX_ADC1_Init();
  MX_TIM4_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
  //HAL_ADC_Start_IT(&hadc1); // ADC Interrupt Start
  // HAL_TIM_Base_Start_IT(&htim4); // Timer Interrupt Start
  HAL_TIM_Base_Start(&htim3); // Timer Start
  HAL_ADC_Start_DMA(&hadc1, bDMA, 2);
  printf("Program Start!\r\n");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  printf("x : %d[%02x %02x], y : %d[%02x %02x] \r\n", bDMA[0].ibuf, bDMA[0].cbuf[0], bDMA[0].cbuf[1],
			                                              bDMA[1].ibuf, bDMA[1].cbuf[0], bDMA[1].cbuf[1]);
	  HAL_Delay(300);
  }
  /* USER CODE END 3 */
}

 

printf("x : %d[%02x %02x], y : %d[%02x %02x] \r\n", bDMA[0].ibuf, bDMA[0].cbuf[0], bDMA[0].cbuf[1],
			                            bDMA[1].ibuf, bDMA[1].cbuf[0], bDMA[1].cbuf[1]);

위와 같이 %d 뒤에 [%02x %02x]를 작성해주면,

Little Endian 형태로 출력되는 것을 직접 볼 수 있다.

 


728x90
반응형