[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
✅ Circular
: 버퍼에 데이터가 누적되었을 때, Overflow되지 않고
다시 버퍼의 처음으로 돌아가서 저장된다.
✅ Driection
: DMA가 어느 쪽으로 요청이 되느냐?
✅ word ≒ int(32bit)
✅ Half word = short(16bit)
ADC Conversion을 구동시켜주는 신호가 필요한데, 적합한 것이 바로 Timer
이때 보내주는 신호를 Trigger라고 한다.
Prescaler, counter는 둘 다 16bit 레지스터
/* 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 */
}
📌 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 실습
우리는 PWM을 타이머를 통해서 제어하고 PWM을 STM 보드의 LD2로 출력할 것이다.
prescaler는 16bit로 65535로 이전과 동일하다. -> 고정값에 가까움
Counter Period -> 가변형에 가까움
하지만 Counter period는 Max값이 훨씬 크다 -> 좀 더 정밀한 제어가 가능함
LD2에 연결된 핀을 TIM2-CH1에 정상적으로 연결됐다.
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // 2번 타이머의 1번 채널의 PWM 기능을 Start해라.
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])을 넣어준다.
/* 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으로 출력
#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 */
}