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

[Harman 세미콘 아카데미] 50일차 - 자율 주행 RC Car Project(UART, Motor, PWM, BlueTooth)

by Graffitio 2023. 9. 5.
[Harman 세미콘 아카데미] 50일차 - 자율 주행 RC Car Project(UART, Motor, PWM, BlueTooth)
728x90
반응형
[개요]

 

 

그동안 배웠던 내용을 바탕으로 자율 주행 커넥티드카를 직접 만들어보는 프로젝트를 시작했다.

실제 과정은 11월부터이지만, 진도가 빠른 관계로 도입부만 미리 맛보기로 했다.

 


 

[UART]

 

📌 Coding

 

/* USER CODE BEGIN PFP */
int __io_putchar(int ch) // 출력 함수
{
	HAL_UART_Transmit(&huart2, &ch, 1, 50);
	return ch;
}

int __io_getchar(void) // 입력 함수
{
	int ch;
	while(1)
	{
		if(HAL_UART_Receive(&huart2, &ch, 1, 50) == HAL_OK) // 받을 때까지 기다렸다가
			break; // 잘 받아지면, break;
	}
	HAL_UART_Transmit(&huart2, &ch, 1, 50); // Echo 기능

	return ch;
}
* USER CODE END PFP */

버퍼 추가

// while문 내에 scanf 함수 호출
int i = 0;
printf("Press Direction key and [Enter]....\r\n"); // 숫자 키를 입력하라고 프롬포트 내보내 줌.
scanf("%c", &i);

 


 

[MOTOR]

 

모터에는 극성이 존재하므로 정회전/역회전 방향을 선택할 수 있으므로

처음 조립할 때, 회전 방향을 맞춰서 조립하는 것이 중요하다.

 

자세한 내용은 아래 링크 참조

Motor Driver 관련 내용

 

[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

 


 

📌 Coding

 

/* USER CODE BEGIN PFP */
void Forward() // 전진
{
	HAL_GPIO_WritePin(Right_Front_GPIO_Port, Right_Front_Pin, 1);
	HAL_GPIO_WritePin(Right_Back_GPIO_Port, Right_Back_Pin, 0);
	HAL_GPIO_WritePin(Left_Front_GPIO_Port, Left_Front_Pin, 0);
	HAL_GPIO_WritePin(Left_Back_GPIO_Port, Left_Back_Pin, 1);
}

void Backward() // 후진
{
	HAL_GPIO_WritePin(Right_Front_GPIO_Port, Right_Front_Pin, 0);
	HAL_GPIO_WritePin(Right_Back_GPIO_Port, Right_Back_Pin, 1);
	HAL_GPIO_WritePin(Left_Front_GPIO_Port, Left_Front_Pin, 1);
	HAL_GPIO_WritePin(Left_Back_GPIO_Port, Left_Back_Pin, 0);
}

void Right() // 우 : 역회전, 좌 : 정회전
{
	HAL_GPIO_WritePin(Right_Front_GPIO_Port, Right_Front_Pin, 0);
	HAL_GPIO_WritePin(Right_Back_GPIO_Port, Right_Back_Pin, 1);
	HAL_GPIO_WritePin(Left_Front_GPIO_Port, Left_Front_Pin, 0);
	HAL_GPIO_WritePin(Left_Back_GPIO_Port, Left_Back_Pin, 1);
}

void Left() // 우 : 정회전, 좌 : 역회전
{
	HAL_GPIO_WritePin(Right_Front_GPIO_Port, Right_Front_Pin, 1);
	HAL_GPIO_WritePin(Right_Back_GPIO_Port, Right_Back_Pin, 0);
	HAL_GPIO_WritePin(Left_Front_GPIO_Port, Left_Front_Pin, 1);
	HAL_GPIO_WritePin(Left_Back_GPIO_Port, Left_Back_Pin, 0);
}
/* USER CODE END PFP */

 

  while (1)
  {
	int i = 0;
	printf("Press Direction key and [Enter]....\r\n"); // 숫자 키를 입력하라고 프롬포트 내보내 줌.
	scanf("%c", &i);

	switch(i)
	{
	case 'w' :
		Forward();
		printf("Move [%c] \r\n", i);
		break;
	case 's' :
		Backward();
		printf("Move [%c] \r\n", i);
		break;
	case 'a' :
		Forward();
		printf("Move [%c] \r\n", i);
		break;
	case 'd' :
		Forward();
		printf("Move [%c] \r\n", i);
		break;
	default :
		printf("Error [%c] \r\n", i);
		break;
	}
  	HAL_Delay(5);
  }

 


 

📌 Result

 

전진, 후진, 좌회전, 우회전

 


 

[PWM 적용]

 

 

System clodk = 100MHz

PSC = 10000-1

ARR = 10000-1

초기에는 주기를 1sec로 설정

이후에 필요에 따라 CCR을 조정하여 모터의 회전 속도를 조절하면 된다.

Duty Cycle = CCR / ARR

 

ARR : Auto Reload Register → Counter Period 값을 의미

CCR : Capture/Compare Register → Pulse 값을 의미

auto-reload preload : PWM 신호를 계속 반복해서 생성하고 싶으면 Enable, 아니면 Disable

 


 

📌 Coding

 

 /* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim10, TIM_CHANNEL_1); // Left
HAL_TIM_PWM_Start(&htim11, TIM_CHANNEL_1); // Right
 /* USER CODE END 2 */

 

while (1)
  {
	int i = 0;
	printf("Press Direction key and [Enter]....\r\n"); // 숫자 키를 입력하라고 프롬포트 내보내 줌.
	scanf("%c", &i);
    
	switch(i)
	{
	case 'w' :
		Forward();
		htim10.Instance->CCR1 = 10000; // Left
		htim11.Instance->CCR1 = 10000; // Right
		printf("Move [%c] \r\n", i);
		break;
	case 's' :
		Backward();
		htim10.Instance->CCR1 = 10000; // Left
		htim11.Instance->CCR1 = 10000; // Right
		printf("Move [%c] \r\n", i);
		break;
	case 'a' :
		Forward();
		htim10.Instance->CCR1 = 9000; // Left
		htim11.Instance->CCR1 = 10000; // Right
		printf("Move [%c] \r\n", i);
		break;
	case 'd' :
		Forward();
		htim10.Instance->CCR1 = 10000; // Left
		htim11.Instance->CCR1 = 9000; // Right
		printf("Move [%c] \r\n", i);
		break;
	default :
		printf("Error [%c] \r\n", i);
		break;
	}
    HAL_Delay(5);
  }

 

cf) Motor는 LED와 달리, 60%이상부터 사용하는 것이 좋다.

 


 

📌 Result

 

 


 

[Bluetooth]

 

UART 사용 방법은 아래와 같이 3가지 방식이 있다.

① 폴링 방식

② 인터럽트 방식

③ DMA 방식

이 중에서 DMA 방식을 사용해보자

 

Bluetooth는 UART1으로 연결

 


 

📌 Coding

 

1. DMA로 사용할 버퍼 선언

/* USER CODE BEGIN PV */
char buf[1]; // DMA buffer
/* USER CODE END PV */

 

2. Receive 함수 호출

  HAL_UART_Receive_DMA(&huart1, buf, sizeof(buf));
  HAL_UART_Receive_DMA(&huart2, buf, sizeof(buf));

    UART 통신을 통하여 데이터를 받고,

    받은 데이터를 buf에 저장하고 필요할 때 꺼내서 사용할 것이다.

 

3. buf에 저장된 값을 불러와서 사용

 while (1)
  {
	if(buf[0] != 0)
	{
		switch(buf[0])
		{
		case 'w' :
			Forward();
			htim10.Instance->CCR1 = 10000; // Left
			htim11.Instance->CCR1 = 10000; // Right
			printf("Forward \r\n");
			break;
		case 's' :
			Backward();
			htim10.Instance->CCR1 = 10000; // Left
			htim11.Instance->CCR1 = 10000; // Right
			printf("Backward \r\n");
			break;
		case 'a' :
			Forward();
			htim10.Instance->CCR1 = 7500; // Left
			htim11.Instance->CCR1 = 10000; // Right
			printf("Right \r\n");
			break;
		case 'd' :
			Forward();
			htim10.Instance->CCR1 = 10000; // Left
			htim11.Instance->CCR1 = 7500; // Right
			printf("Left \r\n");
			break;
		default :
			printf("Error \r\n");
			break;
		}
	}
	buf[0] = 0;
    HAL_Delay(100);
 }

    마지막에 buf[0]을 다시 0으로 초기화해줘야 switch-case문의 printf 내용들이 무한출력되지 않는다.

 


 

📌 Bluetooth App

 

   블루투스 어플은 아래 사진의 APP을 사용하였다.

아두이노용 블루투스 어플리케이션

 


 

📌 Result

 

 


 

[오류 해결]

 

📌 4번씩 입력해야 동작했던 오류

 

오류 사항

 

위 사진과 같이, 4번에 1번 꼴로 제대로 값이 입력되는 문제가 발생하였다.

 

해결

 

위와 같이 int의 데이터 타입을 char로 변경하여 오류 해결하였음.

배열이 1개짜리 buf라 괜찮을 것이라 생각했는데, 

내가 입력하는 값은 문자 하나로 1byte의 크기를 가진다.

int의 변수형은 4byte의 크기를 가지므로 char형을 4번 저장하므로

4번에 1번씩 동작하는 결과가 도출되었던 것이다.

 

변경 후 문제 해결

 


 

📌 블루투스 동작 오류

 

오류 사항

 

코드를 문제없이 작성하였지만, 블루투스 입출력이 제대로 이루어지지 않는 문제가 발생하였다.

 

해결

 

 

예를 들어,

스마트폰에서 전진 신호('w')를 보내려면,

블루투스 모듈의 송신 포트(Tx)에서 보내고 STM 보드의 수신 포트(Rx)에서 받아야 한다.

따라서 Tx는 Tx끼리 Rx는 Rx끼리 연결하면 안 되고,

서로 반대로 연결해 주어야 데이터의 송수신이 원활하게 일어날 수 있다. 

 

 


 

[Full Code]

 

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */

/* USER CODE BEGIN PV */
char buf[1]; // DMA 버퍼
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

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

int __io_getchar(void)
{
	int ch; // 버퍼에 저장됨과 동시에, ch에도 저장
	while(1)
	{
		if(HAL_UART_Receive(&huart2, &ch, 1, 50) == HAL_OK)
			break;
	}
	HAL_UART_Transmit(&huart2, &ch, 1, 50);

	return ch;
}

void Forward()
{
	HAL_GPIO_WritePin(Right_Front_GPIO_Port, Right_Front_Pin, 1);
	HAL_GPIO_WritePin(Right_Back_GPIO_Port, Right_Back_Pin, 0);
	HAL_GPIO_WritePin(Left_Front_GPIO_Port, Left_Front_Pin, 0);
	HAL_GPIO_WritePin(Left_Back_GPIO_Port, Left_Back_Pin, 1);
}

void Backward()
{
	HAL_GPIO_WritePin(Right_Front_GPIO_Port, Right_Front_Pin, 0);
	HAL_GPIO_WritePin(Right_Back_GPIO_Port, Right_Back_Pin, 1);
	HAL_GPIO_WritePin(Left_Front_GPIO_Port, Left_Front_Pin, 1);
	HAL_GPIO_WritePin(Left_Back_GPIO_Port, Left_Back_Pin, 0);
}

int main(void)
{
  HAL_Init();

  SystemClock_Config();

  /* 모든 구성된 주변 장치 초기화 */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_TIM10_Init();
  MX_TIM11_Init();
  MX_USART2_UART_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  printf("Program Start!\r\n");
  HAL_TIM_PWM_Start(&htim10, TIM_CHANNEL_1); // 왼쪽 모터용 PWM 시작
  HAL_TIM_PWM_Start(&htim11, TIM_CHANNEL_1); // 오른쪽 모터용 PWM 시작

  HAL_UART_Receive_DMA(&huart1, buf, sizeof(buf)); // UART1 DMA 수신 시작
  HAL_UART_Receive_DMA(&huart2, buf, sizeof(buf)); // UART2 DMA 수신 시작

  while (1)
  {
	if(buf[0] != 0)
	{
		switch(buf[0])
		{
		case 'w' :
			Forward();
			htim10.Instance->CCR1 = 10000; // 왼쪽 모터 PWM 듀티 사이클 설정
			htim11.Instance->CCR1 = 10000; // 오른쪽 모터 PWM 듀티 사이클 설정
			printf("Forward \r\n");
			break;
		case 's' :
			Backward();
			htim10.Instance->CCR1 = 10000; // 왼쪽 모터 PWM 듀티 사이클 설정
			htim11.Instance->CCR1 = 10000; // 오른쪽 모터 PWM 듀티 사이클 설정
			printf("Backward \r\n");
			break;
		case 'a' :
			Forward();
			htim10.Instance->CCR1 = 7500; // 왼쪽 모터 PWM 듀티 사이클 설정
			htim11.Instance->CCR1 = 10000; // 오른쪽 모터 PWM 듀티 사이클 설정
			printf("Right \r\n");
			break;
		case 'd' :
			Forward();
			htim10.Instance->CCR1 = 10000; // 왼쪽 모터 PWM 듀티 사이클 설정
			htim11.Instance->CCR1 = 7500; // 오른쪽 모터 PWM 듀티 사이클 설정
			printf("Left \r\n");
			break;
		default :
			printf("Error \r\n");
			break;
		}
	}
	buf[0] = 0;
  	HAL_Delay(100);
  }
}
728x90
반응형