Recent Posts
printf("ho_tari\n");
Mini Project1 - Washing Machine 본문
(Telechips) AI 시스템 반도체 SW 개발자 교육/ATmega128A mini Project
Mini Project1 - Washing Machine
호타리 2025. 3. 16. 17:592025.03.04 ~ 2025.03.16
ATmega128A를 이용하여 세탁기 (Washing Machine) 구현 프로젝트 진행
🔧 프로젝트 개요
- MCU: ATMega128A
- 구성 요소: 버튼 4개, FND, LED, 초음파 센서, PWM 기반 DC 모터 (L298N), 부저
- 주요 기능
- 세탁기 3단계 작동 시퀀스 (기본/수동 모드) 구현
- FND로 남은 시간 표시 및 애니메이션 출력
- 초음파 센서로 장애물 감지
- PWM 기반 모터 제어 (정회전/역회전)
- 버튼을 통한 사용자 입력 조작
🚀 주요 동작 흐름
1. 초기화 및 대기 상태
- 전원 ON 전까지 모든 장치는 대기 모드(system_on = 0)
- 전원이 켜지면 Idle 상태 진입 (phase = 0)
- FND에는 현재 선택된 phase (또는 STOP)가 표시됨
2. 버튼 기능
버튼 번호기능
버튼 0 | 기본 시퀀스 실행 (1→2→3 순차 작동) |
버튼 1 | 수동 모드에서 Phase(1~3) 선택 |
버튼 2 | 수동 모드에서 선택된 Phase 실행 |
버튼 3 | 전체 시스템 전원 ON/OFF 토글 |
3. 세탁 동작 시퀀스 (Phase)
✅ Phase 1: 정회전 (20초)
- 모터: 정회전
- LED: PA0 ON
- FND2: 정회전 애니메이션
- FND1: 남은 시간 카운트
🔁 Phase 2: 정/역 반복 회전 (20초)
- 모터: 3초 간격으로 방향 전환
- LED: PA1 ON
- FND2: 정/역 회전 애니메이션
- FND1: 남은 시간 카운트
🔄 Phase 3: 역회전 (20초)
- 모터: 역회전
- LED: PA2 ON
- FND2: 역회전 애니메이션
- FND1: 남은 시간 카운트
⏹️ Idle 또는 종료
- FND에 STOP 표시
- 모든 모터, LED OFF
📟 FND 출력 구성
위치역할
FND1 (상단 2자리) | 남은 시간 or 선택 Phase |
FND2 (하단 2자리) | 회전 애니메이션 (big_circle_forward, backward, stop) |
🌊 초음파 센서 (ultrasonic.c)
- TRIG: PG4, ECHO: PE4 (INT4)
- 외부 인터럽트를 통해 거리 측정
- 사용 용도:
- 장애물 감지 가능 (현재는 FND나 buzzer와 직접 연동은 없음, 확장 가능성 有)
🔊 부저 (buzzer.c)
- 버튼 입력 시 피드백용으로 beep() 함수 사용
- 상태 변화 시 짧은 경고음 제공
💡 타이머 및 인터럽트 사용
- Timer0: 1ms 단위로 msec_count, sec_count, msec2_count 증가
- Timer3: 모터 PWM 제어 (OCR3C 사용)
- INT4: 초음파 ECHO 신호 측정 (상승/하강 edge 감지)
⚙️ 하드웨어 포트 요약
기능핀
버튼 입력 | PORTD.3~6 |
LED 출력 | PORTA.0~2 |
FND 데이터 | PORTC |
FND 자릿수 제어 | PORTB |
모터 제어 | PORTF.6, 7 |
PWM 출력 | PORTE.3 (OC3C) |
초음파 TRIG | PORTG.4 |
초음파 ECHO | PORTE.4 (INT4) |
<프로젝트 정리 자료>
ATmega128A_WashingMachineProject_박성호.pdf
1.48MB
<main.c>
#define MAX_SPEED_FACTOR 10 // 최대 속도 배율 설정
#define MIN_SPEED_FACTOR 1 // 최소 속도 배율
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include "button.h"
void init(void);
extern void init_led(void);
extern int led_main(void);
extern void init_button(void);
extern int get_button(int button_num, int button_pin);
extern void led_all_on(void);
extern void led_all_off(void);
extern void shift_left_ledon(void);
extern void shift_right_ledon(void);
extern void shift_left_keep_ledon(void);
extern void shift_right_keep_ledon(void);
extern void flower_on(void);
extern void flower_off(void);
extern int fnd_main(void);
extern void init_uart0(void);
extern void UART0_transmit(uint8_t data);
extern void (*current_function)(void);
extern init_ultrasonic();
extern void distance_ultrasonic();
extern void distance_led();
extern void fnd_distance_display();
extern void init_fnd(void);
extern void make_pwm_led_control(void);
extern void pwm_fan_control_main(void);
extern void L298N_pwm_fan_control_main(void);
extern void fnd_display(void);
extern volatile uint8_t rx_buff[80];
extern volatile uint8_t rx_msg_received;
volatile int msec_count = 0;
volatile int sec_count = 0;
volatile int msec2_count = 0;
volatile int sec2_count = 0;
volatile int msec2_speed_factor = 1; // 기본 속도 1배
volatile uint32_t closed_check_timer = 0;
volatile uint32_t check_timer = 0; // 5000ms에 한번씩
//extern uint32_t sec_count;
FILE OUTPUT = FDEV_SETUP_STREAM(UART0_transmit, NULL, _FDEV_SETUP_WRITE);
int led_toggle=0;
ISR(TIMER0_OVF_vect)
{
TCNT0=6;
msec_count++;
msec2_count += msec2_speed_factor; // 버튼에 따라 속도 조절
if(msec_count > 1000)
{
msec_count = 0;
sec_count++;
}
if(msec2_count > 1000)
{
msec2_count = 0;
sec2_count++;
}
}
int main(void)
{
init();
//make_pwm_led_control();
//pwm_fan_control_main();
L298N_pwm_fan_fnd_control_main();
while(1)
{
}
}
void init_timer0(void)
{
TCNT0 = 6;
TCCR0 |= 1 << CS02 | 0 << CS01 | 0 << CS00;
TIMSK |= 1 << TOIE0;
}
void init(void)
{
init_timer0();
init_uart0();
init_ultrasonic();
stdout = &OUTPUT;
sei();
init_led();
init_fnd();
}
<pwm.c>
#include "pwm.h"
#include "buzzer.h"
#define MAX_SPEED_FACTOR 10
#define MIN_SPEED_FACTOR 1
#define PRESET_TIME 8
#define SUBPHASE_TIME 2
extern void init_button(void);
extern int get_button(int button_num, int button_pin);
extern void fnd_display(void);
extern void fnd_big_circle_forward(void);
extern void fnd_big_circle_backward(void);
extern void fnd_stop_display(void);
void init_timer3(void);
void L298N_pwm_fan_fnd_control_main(void);
void init_L298N(void);
extern volatile int msec_count;
extern volatile int sec_count;
extern volatile int msec2_count;
extern volatile int sec2_count;
extern volatile int msec2_speed_factor;
extern volatile int display_time;
volatile int temp = 0;
uint8_t phase = 0;
uint8_t selected_phase = 0;
uint8_t manual_mode = 0;
uint8_t system_on = 0;
void init_timer3(void)
{
DDRE |= (1 << 3) | (1 << 5);
TCCR3A |= 1 << WGM30;
TCCR3B |= 1 << WGM32;
TCCR3A |= 1 << COM3C1;
TCCR3B |= (1 << CS31) | (1 << CS30);
OCR3C = 0;
}
void init_L298N(void)
{
DDRF |= (1 << 6) | (1 << 7);
PORTF &= ~((1 << 6) | (1 << 7));
PORTF |= (1 << 6);
}
void L298N_pwm_fan_fnd_control_main(void)
{
uint8_t forward = 1;
unsigned int phase_start_sec = 0;
init_button();
init_timer3();
init_L298N();
init_buzzer();
DDRA |= (1 << 0) | (1 << 1) | (1 << 2);
while(1)
{
if(get_button(BUTTON3, BUTTON3PIN)) {
beep(100);
system_on = !system_on;
_delay_ms(200);
}
if(system_on == 0) {
OCR3C = 0;
fnd_stop_display();
PORTA &= ~((1 << 0) | (1 << 1) | (1 << 2));
phase = 0;
selected_phase = 0;
continue;
}
if(phase == 0) {
OCR3C = 0;
display_time = selected_phase;
fnd_stop_display();
PORTA &= ~((1 << 0) | (1 << 1) | (1 << 2));
if(get_button(BUTTON0, BUTTON0PIN)) {
beep(100);
phase = 1;
manual_mode = 0;
phase_start_sec = sec2_count;
display_time = PRESET_TIME;
forward = 1;
PORTF &= ~((1 << 6) | (1 << 7));
PORTF |= (1 << 6);
OCR3C = 250;
PORTA |= (1 << 0);
_delay_us(100);
}
if(get_button(BUTTON1, BUTTON1PIN)) {
beep(100);
selected_phase++;
if(selected_phase > 3) selected_phase = 1;
_delay_us(100);
}
if(get_button(BUTTON2, BUTTON2PIN)) {
beep(100);
if(selected_phase != 0) {
phase = selected_phase;
manual_mode = 1;
phase_start_sec = sec2_count;
display_time = PRESET_TIME;
if(phase == 1) {
forward = 1;
PORTF &= ~((1 << 6) | (1 << 7));
PORTF |= (1 << 6);
OCR3C = 250;
PORTA |= (1 << 0);
} else if(phase == 2) {
forward = 1;
PORTF &= ~((1 << 6) | (1 << 7));
PORTF |= (1 << 6);
OCR3C = 250;
PORTA |= (1 << 1);
} else if(phase == 3) {
forward = 0;
PORTF &= ~((1 << 6) | (1 << 7));
PORTF |= (1 << 7);
OCR3C = 250;
PORTA |= (1 << 2);
}
_delay_us(100);
}
}
}
else if(phase == 1) {
int elapsed = sec2_count - phase_start_sec;
int remaining = PRESET_TIME - elapsed;
if(remaining > 0) {
display_time = remaining;
} else {
display_time = 0;
fnd_stop_display();
OCR3C = 0;
PORTA &= ~(1 << 0);
beep(100);
_delay_us(100);
if(manual_mode == 0) {
phase = 2;
phase_start_sec = sec2_count;
display_time = PRESET_TIME;
forward = 1;
PORTF &= ~((1 << 6) | (1 << 7));
PORTF |= (1 << 6);
OCR3C = 250;
PORTA |= (1 << 1);
} else {
phase = 0;
selected_phase = 0;
}
}
fnd_big_circle_forward();
}
else if(phase == 2) {
int elapsed = sec2_count - phase_start_sec;
int remaining = PRESET_TIME - elapsed;
if(remaining > 0) {
display_time = remaining;
int subphase = (elapsed / SUBPHASE_TIME) % 2;
if(subphase == 0) {
if(forward != 1) {
forward = 1;
PORTF &= ~((1 << 6) | (1 << 7));
PORTF |= (1 << 6);
}
} else {
if(forward != 0) {
forward = 0;
PORTF &= ~((1 << 6) | (1 << 7));
PORTF |= (1 << 7);
}
}
OCR3C = 250;
if(forward)
fnd_big_circle_forward();
else
fnd_big_circle_backward();
} else {
display_time = 0;
fnd_stop_display();
OCR3C = 0;
PORTA &= ~(1 << 1);
beep(100);
_delay_us(100);
if(manual_mode == 0) {
phase = 3;
phase_start_sec = sec2_count;
display_time = PRESET_TIME;
forward = 0;
PORTF &= ~((1 << 6) | (1 << 7));
PORTF |= (1 << 7);
OCR3C = 250;
PORTA |= (1 << 2);
} else {
phase = 0;
selected_phase = 0;
}
}
}
else if(phase == 3) {
int elapsed = sec2_count - phase_start_sec;
int remaining = PRESET_TIME - elapsed;
if(remaining > 0) {
display_time = remaining;
} else {
display_time = 0;
fnd_stop_display();
OCR3C = 0;
PORTA &= ~(1 << 2);
beep(100);
_delay_us(100);
phase = 0;
selected_phase = 0;
}
fnd_big_circle_backward();
}
fnd_display();
_delay_us(500);
}
}
<fnd.c>
#include "fnd.h"
void init_fnd(void);
int fnd_main(void);
void fnd_display(void);
void fnd_big_circle_forward(void);
void fnd_big_circle_backward(void);
void fnd_stop_display(void);
extern volatile int msec_count;
extern volatile int sec_count;
extern volatile int msec2_count;
extern volatile int sec2_count;
volatile int temp;
volatile int display_time = 0;
int fnd_main(void)
{
while (1)
{
fnd_display();
_delay_ms(1);
fnd_big_circle_backward();
_delay_ms(1);
}
return 0;
}
void init_fnd(void)
{
FND_DATA_DDR = 0xff;
FND_DIGIT_DDR |= 1 << FND_DIGIT_D1 | 1 << FND_DIGIT_D2 | 1 << FND_DIGIT_D3 | 1 << FND_DIGIT_D4;
FND_DIGIT_DDR |= 1 << New_FND_DIGIT_D1 | 1 << New_FND_DIGIT_D2 | 1 << New_FND_DIGIT_D3 | 1 << New_FND_DIGIT_D4;
FND_DIGIT_PORT = ~0x00;
}
void fnd_display(void)
{
uint8_t fnd_font[] = {0xc0, 0xf9, 0xA4, 0xb0, 0x99, 0x92, 0x82, 0xd8, 0x80, 0x90, 0x7f};
static int digit_select = 0;
int value = display_time;
switch(digit_select)
{
case 0:
FND_DIGIT_PORT = 0x80;
FND_DATA_PORT = fnd_font[value % 10];
break;
case 1:
FND_DIGIT_PORT = 0x40;
FND_DATA_PORT = fnd_font[(value / 10) % 10];
break;
case 2:
FND_DIGIT_PORT = 0x20;
FND_DATA_PORT = fnd_font[0];
break;
case 3:
FND_DIGIT_PORT = 0x10;
FND_DATA_PORT = fnd_font[0];
break;
}
digit_select++;
digit_select %= 4;
}
void fnd_big_circle_forward(void)
{
uint8_t fnd_font0[] = {0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf6, 0xf4, 0xf0};
uint8_t fnd_font1[] = {0xff, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf6, 0xf6, 0xf6, 0xf6};
uint8_t fnd_font2[] = {0xff, 0xff, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6};
uint8_t fnd_font3[] = {0xff, 0xff, 0xff, 0xf7, 0xe7, 0xc7, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6};
static int digit_select = 0;
int frame = (msec2_count / 50) % 12;
switch(digit_select)
{
case 0:
FND_DIGIT_PORT = 0x08;
FND_DATA_PORT = fnd_font0[frame];
break;
case 1:
FND_DIGIT_PORT = 0x04;
FND_DATA_PORT = fnd_font1[frame];
break;
case 2:
FND_DIGIT_PORT = 0x02;
FND_DATA_PORT = fnd_font2[frame];
break;
case 3:
FND_DIGIT_PORT = 0x01;
FND_DATA_PORT = fnd_font3[frame];
break;
}
digit_select++;
digit_select %= 4;
}
void fnd_big_circle_backward(void)
{
uint8_t fnd_font0[] = {0xfb, 0xf9, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf0};
uint8_t fnd_font1[] = {0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xf6, 0xf6};
uint8_t fnd_font2[] = {0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xf6, 0xf6, 0xf6};
uint8_t fnd_font3[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xde, 0xce, 0xc6, 0xc6, 0xc6, 0xc6};
static int digit_select = 0;
int frame = (msec2_count / 50) % 12;
switch(digit_select)
{
case 0:
FND_DIGIT_PORT = 0x08;
FND_DATA_PORT = fnd_font0[frame];
break;
case 1:
FND_DIGIT_PORT = 0x04;
FND_DATA_PORT = fnd_font1[frame];
break;
case 2:
FND_DIGIT_PORT = 0x02;
FND_DATA_PORT = fnd_font2[frame];
break;
case 3:
FND_DIGIT_PORT = 0x01;
FND_DATA_PORT = fnd_font3[frame];
break;
}
digit_select++;
digit_select %= 4;S
}
void fnd_stop_display(void)
{
uint8_t fnd_font0 = 0x8c;
uint8_t fnd_font1 = 0xc0;
uint8_t fnd_font2 = 0x87;
uint8_t fnd_font3 = 0x92;
static int digit_select = 0;
switch(digit_select)
{
case 0:
FND_DIGIT_PORT = 0x08;
FND_DATA_PORT = fnd_font0;
break;
case 1:
FND_DIGIT_PORT = 0x04;
FND_DATA_PORT = fnd_font1;
break;
case 2:
FND_DIGIT_PORT = 0x02;
FND_DATA_PORT = fnd_font2;
break;
case 3:
FND_DIGIT_PORT = 0x01;
FND_DATA_PORT = fnd_font3;
break;
}
digit_select++;
digit_select %= 4;
msec2_count = temp;
}
<buzzer.h>
/*
* buzzer.h
*
* Created: 2025-03-15 오후 1:22:37
* Author: microsoft
*/
#ifndef BUZZER_H_
#define BUZZER_H_
#include <util/delay.h>
#define BUZZER_PIN 3
#define BUZZER_PORT PORTE
#define BUZZER_DDR DDRE
// 부저를 초기화합니다.
void init_buzzer(void) {
BUZZER_DDR |= (1 << BUZZER_PIN); // 부저 핀 출력으로 설정
BUZZER_PORT &= ~(1 << BUZZER_PIN); // 부저 OFF (low)
}
// duration_ms 동안 부저로 beep음을 발생시킵니다.
// 예: 2kHz 사각파 (약 250us high, 250us low)
void beep(uint16_t duration_ms) {
// 500us period = 2kHz, 따라서 duration_ms*1000/500번 반복
uint16_t cycles = (duration_ms * 1000UL) / 500;
for(uint16_t i = 0; i < cycles; i++){
BUZZER_PORT |= (1 << BUZZER_PIN);
_delay_us(250);
BUZZER_PORT &= ~(1 << BUZZER_PIN);
_delay_us(250);
}
}
#endif /* BUZZER_H_ */
<speaker.c>
#define F_CPU 16000000L
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#define DO_01 1911
#define DO_01_H 1817
#define RE_01 1703
#define RE_01_H 1607
#define MI_01 1517
#define FA_01 1432
#define FA_01_H 1352
#define SO_01 1276
#define SO_01_H 1199
#define LA_01 1136
#define LA_01_H 1073
#define TI_01 1012
#define DO_02 956
#define DO_02_H 909
#define RE_02 851
#define RE_02_H 804
#define MI_02 758
#define FA_02 716
#define FA_02_H 676
#define SO_02 638
#define SO_02_H 602
#define LA_02 568
#define LA_02_H 536
#define TI_02 506
#define DO_03 478
#define DO_03_H 450
#define RE_03 425
#define RE_03_H 401
#define MI_03 378
#define F_CLK 16000000L //클럭
#define F_SCALER 8 //프리스케일러
#define BEAT_1_32 42
#define BEAT_1_16 86
#define BEAT_1_8 170
#define BEAT_1_4 341
#define BEAT_1_2 682
#define BEAT_1 1364
int laundry[] = {DO_02_H, FA_02_H, FA_02, RE_02_H, DO_02_H, LA_01_H,
TI_01, DO_02_H, RE_02_H, SO_01, LA_01_H, TI_01, LA_01_H, DO_02_H,
DO_02_H, FA_02_H, FA_02, RE_02_H, DO_02_H, FA_02_H,
FA_02_H, SO_02_H, FA_01_H, FA_02, RE_02_H, FA_02, FA_02_H, '\0'};
const int laundry_beats[] = {BEAT_1_4, BEAT_1_8, BEAT_1_8, BEAT_1_8, BEAT_1_4, BEAT_1_4,
BEAT_1_8, BEAT_1_8, BEAT_1_8, BEAT_1_8, BEAT_1_8, BEAT_1_8, BEAT_1_4, BEAT_1_4,
BEAT_1_4, BEAT_1_8, BEAT_1_8, BEAT_1_8, BEAT_1_4, BEAT_1_4,
BEAT_1_8, BEAT_1_8, BEAT_1_8, BEAT_1_8, BEAT_1_8, BEAT_1_8, BEAT_1_2};
void Music_Player(int *tone, int *Beats)
{
while(*tone != '/0')
{
OCR3A = *tone;
delay_ms(*Beats);
tone++;
Beats++;
OCR3A = 0;
_delay_ms(10);
}
return;
}
// Timer3 위상교정 PWM
void init_speaker(void)
{
DDRE |= 0x08; // PWM CHANNEL OC3A(PE3) 출력 모드로 설정 한다.
TCCR3A = (1<<COM3A0); // COM3A0 : 비교일치시 PE3 출력 반전 (P328 표14-6 참고)
TCCR3B = (1<<WGM32) | (1<<CS31); // WGM32 : CTC 4(P327 표14-5) CS31: 8분주(P318)
// CTC mode : 비교일치가 되면 카운터는 reset되면서 PWM 파형 출력 핀의 출력이 반전 됨.
// 정상모드와 CTC모드의 차이점은 비교일치 발생시 TCNT1의 레지스터값을 0으로 설정 하는지 여부 이다.
// 정상모드를 사용시 TCNT1이 자동으로 0으로 설정 되지 않아 인터럽트 루틴에서 TCNT1을 0으로 설정 해 주었다.
// 위상교정 PWM mode4 CTC 분주비 8 16000000/8 ==> 2,000,000HZ(2000KHZ) :
// up-dounting: 비교일치시 LOW, down-counting시 HIGH출력
// 1/2000000 ==> 0.0000005sec (0.5us)
// P599 TOP 값 계산 참고
// PWM주파수 = OSC(16M) / ( 2(up.down)x N(분주율)x(1+TOP) )
// TOP = (fOSC(16M) / 2(up.down)x N(분주율)x 원하는주파수 )) -1
//-----------------------------------------------------------
// - BOTTOM : 카운터가 0x00/0x0000 일때를 가리킨다.
// - MAX : 카운터가 0xFF/0xFFFF 일 때를 가리킨다.
// - TOP?: 카운터가 가질 수 있는 최대값을 가리킨다. 오버플로우 인터럽트의 경우 TOP은 0xFF/0xFFFF
// 이지만 비교일치 인터럽트의 경우 사용자가 설정한 값이 된다.
TCCR3C = 0; // P328 그림 14-11 참고
OCR3A = 0; // 비교 일치값을 OCR3A에 넣는다.
return;
}
void delay_ms(int ms)
{
while(ms-- != 0)_delay_ms(1);
}
<실행 결과>
https://youtube.com/shorts/i7M0yNZkVg0
'(Telechips) AI 시스템 반도체 SW 개발자 교육 > ATmega128A mini Project' 카테고리의 다른 글
Mini Project2 - Auto Car (0) | 2025.03.24 |
---|