1. 모듈 구동
1.1 모터 모듈 개발
L9110s 모듈은 양방향 DC 모터 제어에 사용됩니다. 모듈의 작동 원리는 다음과 같습니다:
- IA1에 하이 레벨 입력, IB1에 로우 레벨 입력 시 모터 정방향 회전
- IA1에 로우 레벨 입력, IB1에 하이 레벨 입력 시 모터 역방향 회전
연결 방식:
- B-1A -- PA0
- B-1B -- PB1
- A-1A -- PA1
- A-1B -- PB10
기본 동작 코드:
motor.c
#include "motor.h"
void 자동차_전진(void)
{
// 좌측 모터
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
// 우측 모터
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
void 자동차_후진(void)
{
// 좌측 모터
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
// 우측 모터
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
}
void 자동차_좌회전(void)
{
// 좌측 모터
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
// 우측 모터
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
void 자동차_우회전(void)
{
// 좌측 모터
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
// 우측 모터
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
void 자동차_정지(void)
{
// 좌측 모터
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
// 우측 모터
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
1.2 PWM 속도 제어
PWM(펄스 폭 변조)를 사용한 모터 속도 제어 원리:
- 전체 속도로 전진: LeftCon1A = 0; LeftCon1B = 1;
- 완전 정지: LeftCon1A = 0; LeftCon1B = 0;
- 주기(예: 20ms) 내에서 전진 시간이 길수록 속도가 빨라짐
PWM 속도 제어 코드:
// 메인 함수 내
TIM_HandleTypeDef htim2;
void PWM_속도_제어_테스트(void)
{
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
while (1)
{
// 저속
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 8);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 8);
HAL_Delay(1000);
// 중속
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 15);
__HAL_TIM_SetCompare(&htim2, CHANNEL_2, 15);
HAL_Delay(1000);
// 고속
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 25);
__HAL_TIM_SetCompare(&htim2, CHANNEL_2, 25);
HAL_Delay(1000);
}
}
1.3 PWM를 이용한 회전 제어
회전 원리:
- 우회전: 좌측 모터 속도 > 우측 모터 속도
- 좌회전: 우측 모터 속도 > 좌측 모터 속도
PWM 회전 제어 코드:
void 회전_테스트(void)
{
while (1)
{
// 우회전
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 10); // 좌측 모터: 중속
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 20); // 우측 모터: 고속
HAL_Delay(1000);
// 좌회전
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 20); // 좌측 모터: 고속
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 10); // 우측 모터: 중속
HAL_Delay(1000);
}
}
2. 선 추적 자동차
2.1 TCRT5000 센서 소개
TCRT5000 적외선 선 추적 센서 작동 원리:
- 적외선 발 다이오드가 지속적으로 적외선을 발사
- 검은색 선은 적외선을 흡수하므로, 센서는 검은색을 감지하면 하이 레벨 출력
- 흰색 배경은 적외선을 반사하므로, 센서는 흰색을 감지하면 로우 레벨 출력
연결 방식:
- VCC: 전원 양극(3-5V)
- GND: 전원 음극
- DO: TTL 스위치 신호 출력(0 또는 1)
- AO: 아날로그 신호 출력(일반적으로 연결하지 않음)
2.2 선 추적 제어 원리
선 추적 알고리즘:
- 양쪽 센서 모두 흰색 감지: 전진
- 좌측 센서만 검은색 감지: 우회전
- 우측 센서만 검은색 감지: 좌회전
- 양쪽 센서 모두 검은색 감지: 정지
2.3 선 추적 핵심 코드
연결 방식:
- 선 추적 센서(좌측) -- PB3
- 선 추적 센서(우측) -- PB4
#define 좌측_센서값 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)
#define 우측_센서값 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)
void 선_추적_제어(void)
{
if (좌측_센서값 == GPIO_PIN_RESET && 우측_서서값 == GPIO_PIN_RESET)
자동차_전진();
else if (좌측_센서값 == GPIO_PIN_SET && 우측_센서값 == GPIO_PIN_RESET)
자동차_우회전();
else if (좌측_센서값 == GPIO_PIN_RESET && 우측_센서값 == GPIO_PIN_SET)
자동차_좌회전();
else if (좌측_센서값 == GPIO_PIN_SET && 우측_센서값 == GPIO_PIN_SET)
자동차_정지();
}
2.4 부드러운 회전 구현
PWM를 이용한 부드러운 회전 구현:
void 부드러운_회전_제어(void)
{
if(좌측_센서값 == GPIO_PIN_RESET && 우측_센서값 == GPIO_PIN_RESET)
{
// 직진
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 25);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 25);
}
else if(좌측_센서값 == GPIO_PIN_SET && 우측_센서값 == GPIO_PIN_RESET)
{
// 우회전 (좌측 속도 > 우측 속도)
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 20);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 10);
}
else if(좌측_센서값 == GPIO_PIN_RESET && 우측_센서값 == GPIO_PIN_SET)
{
// 좌회전 (우측 속도 > 좌측 속도)
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 10);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 20);
}
else if(좌측_센서값 == GPIO_PIN_SET && 우측_센서값 == GPIO_PIN_SET)
{
// 정지
자동차_정지();
}
}
3. 추종/장애물 회피 자동차
3.1 적외선 장애물 감지 센서
작동 원리:
- 선 추적 센서와 유사하나, 적외선 방향이 수평으로 향함
- 장애물이 가까이 있으면 적외선이 반사되어 로우 레벨 출력
- 장애물이 없으면 적외선이 반사되지 않아 하이 레벨 출력
3.2 추종 자동차 원리
추종 알고리즘:
- 좌측 센서 장애물 감지, 우측 센서 미감지: 우회전
- 우측 센서 장애물 감지, 좌측 센서 미감지: 좌회전
- 양쪽 센서 모두 장애물 감지: 전진
- 양쪽 센서 모두 장애물 미감지: 정지
3.3 추종 자동차 코드
연결 방식:
- 추종 센서(좌측) -- PB5
- 추종 센서(우측) -- PB6
#define 좌측_추종_센서값 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5)
#define 우측_추종_센서값 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6)
void 추종_제어(void)
{
if(좌측_추종_센서값 == GPIO_PIN_RESET && 우측_추종_센서값 == GPIO_PIN_RESET)
자동차_전진();
else if(좌측_추종_센서값 == GPIO_PIN_SET && 우측_추종_센서값 == GPIO_PIN_RESET)
자동차_우회전();
else if(좌측_추종_센서값 == GPIO_PIN_RESET && 우측_추종_센서값 == GPIO_PIN_SET)
자동차_좌회전();
else if(좌측_추종_센서값 == GPIO_PIN_SET && 우측_추종_센서값 == GPIO_PIN_SET)
자동차_정지();
}
3.4 초음파 센서 소개
HC-SR04 초음파 센서 작동 원리:
- Trig 핀에 최소 10us 하이 레벨 신호 발생
- Echo 핀이 로우에서 하이로 변하면 초음파 발사 시작
- Echo 핀이 하이에서 로우로 변하면 초음파 수신 완료
- Echo 핀의 하이 레벨 유지 시간으로 거리 계산
- 거리(cm) = (시간(us) * 340m/s) / 2 / 10000
3.5 서보 모터 소개
SG90 서보 모터 특성:
- PWM 신호로 각도 제어 (주파수 약 50Hz, 주기 20ms)
- 각도 제어 범위: 0° ~ 180°
- 각도별 PWM 펄스 폭:
- 0.5ms: 0° (CCRx = 5)
- 1.0ms: 45° (CCRx = 10)
- 1.5ms: 90° (CCRx = 15)
- 2.0ms: 135° (CCRx = 20)
- 2.5ms: 180° (CCRx = 25)
3.6 장애물 회피 자동차 코드
연결 방식:
- 서보 모터 -- PB9
- 초음파 센서 Trig -- PB7
- 초음파 센서 Echo -- PB8
서보 모터 제어 코드:
servo.c
#include "servo.h"
#include "gpio.h"
#include "tim.h"
void 서보_초기화(void)
{
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_4);
서보_중앙위치();
}
void 서보_중앙위치(void)
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 15); // 90°
}
void 서보_좌측위치(void)
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 5); // 0°
}
void 서보_우측위치(void)
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 25); // 180°
}
초음파 센서 코드:
ultrasonic.c
#include "ultrasonic.h"
#include "gpio.h"
#include "tim.h"
void TIM2_Delay_us(uint16_t us)
{
__HAL_TIM_ENABLE(&htim2);
__HAL_TIM_SetCounter(&htim2, 0);
while(__HAL_TIM_GetCounter(&htim2) < us);
__HAL_TIM_DISABLE(&htim2);
}
double 거리_측정(void)
{
// 1. Trig에 10us 하이 레벨 신호 발생
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
TIM2_Delay_us(20);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
// 2. Echo가 하이 레벨로 변하면 타이머 시작
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_RESET);
HAL_TIM_Base_Start(&htim2);
__HAL_TIM_SetCounter(&htim2, 0);
// 3. Echo가 로우 레벨로 변하면 타이머 정지
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET);
HAL_TIM_Base_Stop(&htim2);
// 4. 거리 계산
uint32_t 시간 = __HAL_TIM_GetCounter(&htim2);
return (시간 * 0.034 / 2); // cm 단위
}
장애물 회피 메인 코드:
#define MIDDLE 0
#define LEFT 1
#define RIGHT 2
char 서보_방향 = MIDDLE;
double 중앙_거리, 좌측_거리, 우측_거리;
void 장애물_회피_제어(void)
{
// 서보 중앙 위치
if(서보_방향 != MIDDLE){
서보_중앙위치();
서보_방향 = MIDDLE;
HAL_Delay(300);
}
중앙_거리 = 거리_측정();
if(중앙_거리 > 35){
// 전진
자동차_전진();
}
else if(중앙_거리 < 10){
// 후진
자동차_후진();
}
else
{
// 정지 후 좌우 거리 측정
자동차_정지();
// 좌측 거리 측정
서보_좌측위치();
HAL_Delay(300);
좌측_거리 = 거리_측정();
// 중앙 복귀
서보_중앙위치();
HAL_Delay(300);
// 우측 거리 측정
서보_우측위치();
서보_방향 = RIGHT;
HAL_Delay(300);
우측_거리 = 거리_측정();
// 방향 결정
if(좌측_거리 < 우측_거리){
자동차_우회전();
HAL_Delay(150);
자동차_정지();
}
else{
자동차_좌회전();
HAL_Delay(150);
자동차_정지();
}
}
HAL_Delay(50);
}
4. 속도 측정 자동차
4.1 인코더 모듈
인코더 모듈 특성:
- 모터 회전 속도 측정에 사용
- 회전체의 구멍을 통과할 때마다 펄스 출력
- 구멍이 있으면 하이 레벨, 없으면 로우 레벨 출력
연결 방식:
- VCC: 3.3V (5V 연결 시 중복 트리거 발생)
- OUT: PB14
4.2 속도 측정 원리
속도 계산 방법:
- 바퀴 1회전 거리 = 2 × π × 반지름 = π × 지름 (6.5cm)
- 인코더 1회전당 20개 펄스
- 1펄스당 이동 거리 = π × 6.5cm / 20 ≈ 1.02cm
- 1초간 펄스 수 측정 → 속도(cm/s) = 펄스 수
4.3 속도 측정 코드
unsigned int 펄스_카운트 = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_14)
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) == GPIO_PIN_RESET)
펄스_카운트++;
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
printf("속도: %d cm/s\n", 펄스_카운트);
펄스_카운트 = 0;
}
// 메인 함수
void 속도_측정_시작(void)
{
HAL_TIM_Base_Start_IT(&htim2); // 1초 주기 타이머 인터럽트 시작
}
4.4 OLED 화면에 속도 표시
연결 방식:
- SCL -- PB6
- SDA -- PB7
OLED 표시 코드:
#include "oled.h"
#include "i2c.h"
#include "oledfont.h"
char 속도_메시지[24];
void 속도_표시(int 속도값)
{
sprintf(속도_메시지, "속도:%2d cm/s", 속도값);
Oled_Show_Str(2, 2, 속도_메시지);
}
// 인터럽트 콜백에서 호출
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
속도_표시(펄스_카운트);
펄스_카운트 = 0;
}
5. 원격 제어 자동차
5.1 블루투스 제어
블루투스 모듈 특성:
- 시리얼 투전 모드로 동작
- AT 명령어로 설정
- 데이터를 그대로 전달 (투전 기능)
블루투스 제어 코드:
#include "bluetooth.h"
#include "motor.h"
char 수신_버퍼[20];
void 블루투스_명령_처리(void)
{
if(!strcmp(수신_버퍼, "전진")){
자동차_전진();
HAL_Delay(10);
}
else if(!strcmp(수신_버퍼, "후진")){
자동차_후진();
HAL_Delay(10);
}
else if(!strcmp(수신_버퍼, "좌회전")){
자동차_좌회전();
HAL_Delay(10);
}
else if(!strcmp(수신_버퍼, "우회전")){
자동차_우회전();
HAL_Delay(10);
}
else{
자동차_정지();
}
}
5.2 Wi-Fi 제어
ESP-01s Wi-Fi 모듈 특성:
- AT 명령어 인터페이스
- AP/Station 모드 지원
- TCP/UDP 프로토콜 지원
Wi-Fi 연결 코드:
void WiFi_연결_설정(void)
{
// Wi-Fi 모드 설정
AT_명령_전송("AT+CWMODE=1\r\n");
HAL_Delay(1000);
// AP 연결
AT_명령_전송("AT+CWJAP=\"SSID\",\"PASSWORD\"\r\n");
HAL_Delay(3000);
// TCP 서버 시작
AT_명령_전송("AT+CIPMUX=1\r\n");
HAL_Delay(1000);
AT_명령_전송("AT+CIPSERVER=1,8080\r\n");
HAL_Delay(1000);
}
5.3 4G 제어
EC03-DNC 4G 모듈 특성:
- 시리얼 AT 명령어 인터페이스
- 투전 모드 지원
- TCP/UDP 프로토콜 지원
4G 제어 코드: (블루투스 코드와 유사하나, 연결 방식만 변경)
void 모듈_초기화(void)
{
// 4G 모듈 시작
AT_명령_전송("AT\r\n");
HAL_Delay(1000);
// 네트워크 연결 확인
AT_명령_전송("AT+CREG?\r\n");
HAL_Delay(1000);
// TCP 서버 연결
AT_명령_전송("AT+CIPSTART=0,\"TCP\",\"서버_IP\",포트\r\n");
HAL_Delay(2000);
}
6. 음성 인식 제어
6.1 음성 모듈 설정
SU-03T/LD3320 음성 인식 모듈 특성:
- 미리 정의된 음성 명령어 인식
- 시리얼 인터페이스
- 최대 10개 명령어 저장 가능
연결 방식:
- 음성 모듈 제어 핀 -- PA15 (추종 모드)
- 음성 모듈 제어 핀 -- PA13 (장애물 회피 모드)
- 음성 모듈 제어 핀 -- PA14 (선 추적 모드)
6.2 음성 제어 코드
#define 추종_모드 1
#define 장애물_회피_모드 2
#define 선_추적_모드 3
int 현재_모드 = 0;
void 음식_인식_모드_전환(int 모드)
{
if(모드 == 추종_모드){
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_14, GPIO_PIN_RESET);
현재_모드 = 추종_모드;
}
else if(모드 == 장애물_회피_모드){
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_13, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_14, GPIO_PIN_RESET);
현재_모드 = 장애물_회피_모드;
}
else if(모드 == 선_추적_모드){
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_14, GPIO_PIN_SET);
현재_모드 = 선_추적_모드;
}
}
void 메인_제어_루프(void)
{
// 음성 명령어에 따른 모드 전환
if(음식_인식_완료()){
if(strcmp(인식된_명령어, "추종") == 0){
음식_인식_모드_전환(추종_모드);
}
else if(strcmp(인식된_명령어, "회피") == 0){
음식_인식_모드_전환(장애물_회피_모드);
}
else if(strcmp(인식된_명령어, "추적") == 0){
음식_인식_모드_전환(선_추적_모드);
}
}
// 현재 모드에 따라 제어
switch(현재_모드){
case 추종_모드:
추종_제어();
break;
case 장애물_회피_모드:
장애물_회피_제어();
break;
case 선_추적_모드:
선_추적_제어();
break;
}
}