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

[Harman 세미콘 아카데미] 47일차 - ARM 및 RTOS 활용(Interrupt, DMA, PWM)

by Graffitio 2023. 8. 31.
[Harman 세미콘 아카데미] 47일차 - ARM 및 RTOS 활용(Interrupt, DMA, PWM)
728x90
반응형
[Interrupt]

 

void JoyStick(int op)
{
	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(op)	printf("x : %5d, y : %5d, z : %d \r\n", xVal, yVal, zVal);
}

우리가 만든 JoyStick 함수는 소프트웨어 컨버젼 방식이기 때문에, 함수를 통해서 명령어를 수행할 수 밖에 없다.

 

 

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	zVal = !(HAL_GPIO_ReadPin(z_Value_GPIO_Port, z_Value_Pin));
	if(count == 0) xVal = HAL_ADC_GetValue(&hadc1);
	else if(count == 1)
	{
		yVal = HAL_ADC_GetValue(&hadc1);
		printf("x : %5d, y : %5d, z : %d \r\n", xVal, yVal, zVal);
	}

	count++; // callback 1회 호출 시마다 value 하나씩 가져옴
	if(count > 1) count = 0;
	// if(++count > 1) count = 0; // 이렇게도 쓸 수 있다.
	HAL_ADC_Start_IT(&hadc1);
}

ADC callback 함수에서 HAL_ADC_Start()를 사용 못 하는 이유는

이미 컨버젼이 완료된 상태이기 때문이다.

따라서 Start없이 GetValue 함수를 각각 호출해줘야 한다.

 

Callback 함수의 단점

    한 번 호출되면, Interrupt Flag가 리셋된다.

    따라서 HAL_ADC_Start_IT(&hadc1); 를 추가해줘야

    매 IT 발생 시마다 동작하게 된다.

 

하나씩 값을 가져오기 때문에 약간 쳐져서 출력되는 것을 볼 수 있다.

 


 

📌 Callback 함수

 

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc);

void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc);

 

위와 같이 Callback 함수에는 두 가지 종류가 있다.

(All complete Callback, Half Complete Callback)

 

굳이 두 가지로 나눠져 있는 이유는 특별히 큰 장점이나 기능이 있어서라기보다

코딩하는 스타일에 따라

한 번에 여러 개를 불러올 것이냐 아니면 하나씩 불러오느냐

그 차이로 생각하는 것이 가장 적합하다.

 

// 한 번에 여러 가지 값을 가져 오는 스타일
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	if(count == 0) xVal = HAL_ADC_GetValue(&hadc1);
	else if(count == 1)
	{
		yVal = HAL_ADC_GetValue(&hadc1);
	}
	count++; // callback 1회 호출 시마다 value 하나씩 가져옴
	if(count > 1) count = 0;
	HAL_ADC_Start_IT(&hadc1);
}

 

// 하나씩 값을 불러오는 코딩 스타일
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
	xVal = HAL_ADC_GetValue(&hadc1);
	HAL_ADC_Start_IT(&hadc1);
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	yVal = HAL_ADC_GetValue(&hadc1);
	HAL_ADC_Start_IT(&hadc1);
}

 


 

[DMA]

 

백그라운드에서는 ADC가 알아서 컨버젼하고, 출력은 우리가 원하는 타이밍에 수행되도록 코딩해보도록 하자.

 

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	//zVal = !(HAL_GPIO_ReadPin(z_Value_GPIO_Port, z_Value_Pin));
	if(count == 0) xVal = HAL_ADC_GetValue(&hadc1);
	else if(count == 1)
	{
		yVal = HAL_ADC_GetValue(&hadc1);
		// printf("x : %5d, y : %5d, z : %d \r\n", xVal, yVal, zVal);
	}

	count++; // callback 1회 호출 시마다 value 하나씩 가져옴
	if(count > 1) count = 0;
	// if(++count > 1) count = 0; // 이렇게도 쓸 수 있다.
	HAL_ADC_Start_IT(&hadc1);
}
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	//JoyStick(1);
	zVal = !(HAL_GPIO_ReadPin(z_Value_GPIO_Port, z_Value_Pin));
	printf("x : %5d, y : %5d, z : %d \r\n", xVal, yVal, zVal);
	HAL_Delay(150);
  }

 

이론적으로는 가능한데,

Interrupt 함수와 GPIO ReadPin 함수가 서로 꼬여서 xVal, yVal은 정상적으로 출력되지 않는다.

이러한 문제의 발생을 방지하기 위해 나온 것이 바로 DMA

 

DMA Setting / Circular

✅ Circular

     : 버퍼에 데이터가 누적되었을 때, Overflow되지 않고

       다시 버퍼의 처음으로 돌아가서 저장된다.

✅ Driection

     : DMA가 어느 쪽으로 요청이 되느냐?

✅ word ≒ int(32bit)

✅ Half word = short(16bit)

 

ADC Conversion을 구동시켜주는 신호가 필요한데, 적합한 것이 바로 Timer

이때 보내주는 신호를 Trigger라고 한다.

Prescaler, counter는 둘 다 16bit 레지스터

 

위의 ADC 셋팅에 의해서 자동으로 Enable됨.
ADC셋팅에 맞춰서 TIM의 Trigger 신호도 Update Event로 변경

 

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

TIM_HandleTypeDef htim3;

UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */
int xVal, yVal, zVal;
int count;
short int buf[100];
/* 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_TIM3_Init(void);
/* USER CODE BEGIN PFP */

int __io_putchar(int ch)
{
	HAL_UART_Transmit(&huart2, &ch, 1, 100);
	return ch;
}

void JoyStick(int op)
{
	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(op)	printf("x : %5d, y : %5d, z : %d \r\n", xVal, yVal, zVal);
}

//void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
//{
//	//zVal = !(HAL_GPIO_ReadPin(z_Value_GPIO_Port, z_Value_Pin));
//	if(count == 0) xVal = HAL_ADC_GetValue(&hadc1);
//	else if(count == 1)
//	{
//		yVal = HAL_ADC_GetValue(&hadc1);
//		// printf("x : %5d, y : %5d, z : %d \r\n", xVal, yVal, zVal);
//	}
//
//	count++; // callback 1회 호출 시마다 value 하나씩 가져옴
//	if(count > 1) count = 0;
//	// if(++count > 1) count = 0; // 이렇게도 쓸 수 있다.
//	HAL_ADC_Start_IT(&hadc1);
//}
/* USER CODE END PFP */

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

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
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_TIM3_Init();
  /* USER CODE BEGIN 2 */
  printf("Hello! Program Start! \r\n");
  HAL_TIM_Base_Start(&htim3);
  HAL_ADC_Start_DMA(&hadc1, buf, 2); // 두 개의 신호를 번갈아 가면서 받아야되기 때문에 buf의 총 배열 수인 100을 써버리면 값이 계속 쌓이면서 오류가 발생하게된다.
  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
	//JoyStick(1);
	zVal = !(HAL_GPIO_ReadPin(z_Value_GPIO_Port, z_Value_Pin));
	//printf("x : %5d, y : %5d, z : %d \r\n", xVal, yVal, zVal);
	printf("x : %5d, y : %5d, z : %d \r\n", buf[0], buf[1], zVal);
	HAL_Delay(150);
  }
  /* USER CODE END 3 */
}

0.5초에 1번씩 Timer에 의해서 conversion되고, 변환된 값이 buf(DMA)에 저장된다. 그리고 while문 안의 printf함수에 의해서 150ms에 한 번씩 값들이 출력된다.

 


 

📌 APB1, APB2의 차이점

      APB1과 APB2는 ARM Cortex-M 시리즈 마이크로컨트롤러의 주요 버스인

      Advanced Peripheral Bus 1, 2(APB1, APB2)를 나타내며,

     이들은 주로 페리퍼럴(주변 장치)와의 통신을 담당하는 인터페이스로 사용된다.

 

    ✅ 주요 목적

         -  APB1 

            : 주로 낮은 대역폭의 페리퍼럴과 통신하는 데 사용되며,

              작은 규모의 주변 장치와의 통신에 적합하다.

         - APB2

            : 보다 높은 대역폭이 필요한 주면 장치와의 통신에 사용되며,

              APB2는 APB1보다 더 높은 성능을 제공한다.

 

    ✅ Clock speed

         -  APB1 

            : 일반적으로 더 낮은 클럭 속도로 동작한다.

         - APB2

            : APB1보다 높은 클럭 속도로 동작할 수 있다.

 

    ✅ 페리퍼럴 연결

         -  APB1 

            : 주로 기본적인 페리퍼럴에 연결된다.

              (타이머, 워치독 타이머 등)

         - APB2

            : 더 복잡하거나 대역폭이 높은 페리퍼럴에 연결된다.

              (USART, SPI, I2C 등)

 

    ✅ 전력 소비

         -  APB1 

            : 더 낮은 속도와 대역폭으로 인해 전력 소비가 상대적으로 낮다.

         - APB2

            : 높은 속도와 대역폭으로 인해 상대적으로 전력 소비가 높을 수 있다.

 


 

[PWM]

 

📌 PWM 개념

 

아래 링크 참조

PWM이란?

 

[Harman 세미콘 아카데미] 23일차 - ATmega128(FND count, PWM, Buzzer, Motor driver)

[FND Count] ※ 기능 추가 + FND overfolw 시, LED_Bar로 출력 + Up / Down Count #define F_CPU 16000000UL #include #include #include #defineFND_DATA_DDRDDRA // 데이터 포트 #defineFND_SELECT_DDRDDRF // 셀렉트 포트(자릿수 선택) #defineFND

rangvest.tistory.com

 


 

📌 PWM 실습

우리는 PWM을 타이머를 통해서 제어하고 PWM을 STM 보드의 LD2로 출력할 것이다.

LD2 Pin을 PWM 채널로 설정
모든 타이머가 다 되는 것은 아니고, 타이머는 핀과 연관성이 있기 때문에 핀과 중복되는 타이머는 사용이 제한된다.

 

PWM Generation CH1 클릭

 

PWM Parameter

prescaler는 16bit로 65535로 이전과 동일하다. -> 고정값에 가까움

Counter Period -> 가변형에 가까움

하지만 Counter period는 Max값이 훨씬 크다 -> 좀 더 정밀한 제어가 가능함

 

 

LD2에 연결된 핀을 TIM2-CH1에 정상적으로 연결됐다.

 

TIM2의 ARR값을 변환시켜가면서 듀티비 제어

 

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // 2번 타이머의 1번 채널의 PWM 기능을 Start해라.

 

CCR1 : 채널 레지스터 1번

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_TIM2_Init();
  /* USER CODE BEGIN 2 */
  int arr = 0;
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // 2번 타이머의 1번 채널의 PWM 기능을 Start해라.
  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
	htim2.Instance->CCR1 = arr; // CRR1(CH1 채널 레지스터)에 값을 넣어준다는 의미
	arr += 10; // 듀티비 변화
	if(arr > 9999) arr = 0;
	HAL_Delay(10);
  }
  /* USER CODE END 3 */
}

 


 

[Mission]

 

📌 Mission I

    Q. JoyStick 조작에 따라 LED 점멸 속도가 변화하도록 PWM을 활용하여 설계

 

📌 Result

TIM3를 Trigger로 사용하여 ADC Conversion Value를 buf(DMA)에 저장하고

TIM2-CH1을 PWM Generator로 사용하고 CCR1에 위에서 받은 값(buf[0])을 넣어준다.

 

Pin Configuration

 

ADC Setting

 

TIM3 Setting

 

TIM2 Setting

 

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;

UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */
int xVal, yVal, zVal;
short int buf[100];
/* 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_TIM2_Init(void);
static void MX_TIM3_Init(void);
/* USER CODE BEGIN PFP */
void JoyStick(int op)
{
	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(op)	printf("x : %5d, y : %5d, z : %d \r\n", xVal, yVal, zVal);
}

/* USER CODE END PFP */

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

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
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_TIM2_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */

  printf("Hello! Program Start! \r\n");
  HAL_TIM_Base_Start(&htim3);
  HAL_ADC_Start_DMA(&hadc1, buf, 2);
  HAL_TIM_PWM_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 */
	htim2.Instance->CCR1 = 2*buf[0];
	HAL_Delay(10);

  }
  /* USER CODE END 3 */
}

 


 

📌 Mission II

    Q. JoyStick 조작에 따라 RGB LED의 밝기를 부드럽게 조절하여 모든 색을 출력할 수 있도록 설계

 

📌 Result

ADC Conversion을 실시하고 조이스틱 값을 받는 함수를 정의하고(JoyStick)

TIM2-CH1을 PWM Generator로 사용하고 CCR1에 xVal

TIM1-CH1을 PWM Generator로 사용하고 CCR1에 yVal 받아서

적절한 값으로 변환시켜준 뒤 PWM으로 출력

zVal은 Switch이므로 GPIO ReadPin으로 받고 GPIO WritePin으로 출력

 

Pin Configurarion

 

ADC Setting

 

TIM1_CH1 Setting

 

TIM2_CH1 Setting

 

GPIO Setting

 

#include "main.h"
/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim2;
UART_HandleTypeDef huart2;
/* USER CODE BEGIN PV */
int xVal, yVal, zVal;
/* 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_TIM2_Init(void);
static void MX_TIM1_Init(void);
/* USER CODE BEGIN PFP */
void JoyStick()
{
	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));
}


int main(void)
{
   /* USER CODE BEGIN Init */
int arr1 = 0;
int arr2 = 0;
  /* USER CODE END Init */

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

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_ADC1_Init();
  MX_TIM2_Init();
  MX_TIM1_Init();
  /* USER CODE BEGIN 2 */
  printf("Hello! Program Start! \r\n");
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
	JoyStick(); // xVal, yVal, zVal 값 받는 함수 호출
	arr1 = xVal*1000/4095; // 부드럽게 변환되도록 적절한 값 연산
	arr2 = yVal*1000/4095;
	htim2.Instance->CCR1 = arr1; // CH1 레지스터에 변수 넣어줌
	htim1.Instance->CCR1 = arr2;
	HAL_GPIO_WritePin(z_Value_Out_GPIO_Port,z_Value_Out_Pin , zVal);
	HAL_Delay(10);
  }
  /* USER CODE END 3 */
}

 


 

728x90
반응형